mirror of
https://github.com/EQEmu/Server.git
synced 2026-04-19 21:02:41 +00:00
opening additional merc slots
This commit is contained in:
parent
8eafc8bc7d
commit
804447a6d0
@ -376,6 +376,7 @@ N(OP_MercenaryDismiss),
|
|||||||
N(OP_MercenaryHire),
|
N(OP_MercenaryHire),
|
||||||
N(OP_MercenarySuspendRequest),
|
N(OP_MercenarySuspendRequest),
|
||||||
N(OP_MercenarySuspendResponse),
|
N(OP_MercenarySuspendResponse),
|
||||||
|
N(OP_MercenarySwitch),
|
||||||
N(OP_MercenaryTimer),
|
N(OP_MercenaryTimer),
|
||||||
N(OP_MercenaryTimerRequest),
|
N(OP_MercenaryTimerRequest),
|
||||||
N(OP_MercenaryUnknown1),
|
N(OP_MercenaryUnknown1),
|
||||||
|
|||||||
@ -6236,6 +6236,12 @@ struct SuspendMercenary_Struct {
|
|||||||
/*0001*/
|
/*0001*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// [OPCode: 0x1b37 (RoF2)] [Client->Server] [Size: 4]
|
||||||
|
struct SwitchMercenary_Struct {
|
||||||
|
/*0000*/ uint32 MercIndex; // 0-based UI index into owned merc list
|
||||||
|
/*0004*/
|
||||||
|
};
|
||||||
|
|
||||||
// [OPCode: 0x2528] On Live as of April 2 2012 [Server->Client] [Size: 4]
|
// [OPCode: 0x2528] On Live as of April 2 2012 [Server->Client] [Size: 4]
|
||||||
// Response to suspend merc with timestamp
|
// Response to suspend merc with timestamp
|
||||||
struct SuspendMercenaryResponse_Struct {
|
struct SuspendMercenaryResponse_Struct {
|
||||||
|
|||||||
@ -2323,7 +2323,7 @@ namespace RoF2
|
|||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance);
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[0].MercUnk05); // MercUnk05
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[0].MercUnk05); // MercUnk05 - trailing field (unlocked slot count)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -255,7 +255,7 @@ RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
|
|||||||
RULE_BOOL(Mercs, MercsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.")
|
RULE_BOOL(Mercs, MercsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.")
|
||||||
RULE_INT(Mercs, MercsHasteCap, 100, "Haste cap for non-v3(over haste) haste")
|
RULE_INT(Mercs, MercsHasteCap, 100, "Haste cap for non-v3(over haste) haste")
|
||||||
RULE_INT(Mercs, MercsHastev3Cap, 25, "Haste cap for v3(over haste) haste")
|
RULE_INT(Mercs, MercsHastev3Cap, 25, "Haste cap for v3(over haste) haste")
|
||||||
RULE_INT(Mercs, MaxMercSlots, 6, "Maximum number of mercenary slots per character (max 11)")
|
RULE_INT(Mercs, MaxMercSlots, 6, "Maximum number of mercenary slots per character (max 6)")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Guild)
|
RULE_CATEGORY(Guild)
|
||||||
|
|||||||
@ -418,6 +418,7 @@ OP_MercenaryUnknown1=0x5d26
|
|||||||
OP_MercenaryCommand=0x27f2
|
OP_MercenaryCommand=0x27f2
|
||||||
OP_MercenarySuspendRequest=0x4407
|
OP_MercenarySuspendRequest=0x4407
|
||||||
OP_MercenarySuspendResponse=0x6f03
|
OP_MercenarySuspendResponse=0x6f03
|
||||||
|
OP_MercenarySwitch=0x1b37
|
||||||
OP_MercenaryUnsuspendResponse=0x27a0
|
OP_MercenaryUnsuspendResponse=0x27a0
|
||||||
|
|
||||||
# Looting
|
# Looting
|
||||||
|
|||||||
@ -7958,21 +7958,19 @@ void Client::SendMercPersonalInfo()
|
|||||||
mdus->MercStatus = 0;
|
mdus->MercStatus = 0;
|
||||||
mdus->MercCount = mercCount;
|
mdus->MercCount = mercCount;
|
||||||
|
|
||||||
|
// Lambda to populate a single merc entry in the packet
|
||||||
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||||
uint32 merc_index = 0;
|
uint32 merc_index = 0;
|
||||||
for (int slot = 0; slot < max_slots && merc_index < MAX_MERC; slot++) {
|
|
||||||
auto& info = GetMercInfo(slot);
|
|
||||||
if (info.mercid == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message(Chat::Yellow, "SendMercPersonalInfo: slot %i, mercid %u, templateid %u, name '%s', suspended %i",
|
auto fillMercEntry = [&](int slot) {
|
||||||
slot, info.mercid, info.MercTemplateID, info.merc_name, info.IsSuspended);
|
auto& info = GetMercInfo(slot);
|
||||||
|
if (info.mercid == 0 || merc_index >= MAX_MERC) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto tmpl_it = zone->merc_templates.find(info.MercTemplateID);
|
auto tmpl_it = zone->merc_templates.find(info.MercTemplateID);
|
||||||
if (tmpl_it == zone->merc_templates.end()) {
|
if (tmpl_it == zone->merc_templates.end()) {
|
||||||
Message(Chat::Red, "SendMercPersonalInfo: slot %i template %u NOT FOUND, skipping", slot, info.MercTemplateID);
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MercTemplate *mercData = &tmpl_it->second;
|
MercTemplate *mercData = &tmpl_it->second;
|
||||||
@ -7984,7 +7982,7 @@ void Client::SendMercPersonalInfo()
|
|||||||
|
|
||||||
if (stancecount > MAX_MERC_STANCES) {
|
if (stancecount > MAX_MERC_STANCES) {
|
||||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo: stance count %u exceeds max for slot %i, skipping", stancecount, slot);
|
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo: stance count %u exceeds max for slot %i, skipping", stancecount, slot);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mdus->MercData[merc_index].MercID = mercData->MercTemplateID;
|
mdus->MercData[merc_index].MercID = mercData->MercTemplateID;
|
||||||
@ -7999,7 +7997,7 @@ void Client::SendMercPersonalInfo()
|
|||||||
mdus->MercData[merc_index].MercUnk01 = 0;
|
mdus->MercData[merc_index].MercUnk01 = 0;
|
||||||
mdus->MercData[merc_index].TimeLeft = info.MercTimerRemaining;
|
mdus->MercData[merc_index].TimeLeft = info.MercTimerRemaining;
|
||||||
mdus->MercData[merc_index].MerchantSlot = merc_index + 1;
|
mdus->MercData[merc_index].MerchantSlot = merc_index + 1;
|
||||||
mdus->MercData[merc_index].MercUnk02 = 1;
|
mdus->MercData[merc_index].MercUnk02 = (slot == GetMercSlot()) ? 1 : 0;
|
||||||
mdus->MercData[merc_index].StanceCount = stancecount;
|
mdus->MercData[merc_index].StanceCount = stancecount;
|
||||||
mdus->MercData[merc_index].MercUnk03 = 0;
|
mdus->MercData[merc_index].MercUnk03 = 0;
|
||||||
mdus->MercData[merc_index].MercUnk04 = 1;
|
mdus->MercData[merc_index].MercUnk04 = 1;
|
||||||
@ -8017,6 +8015,20 @@ void Client::SendMercPersonalInfo()
|
|||||||
|
|
||||||
mdus->MercData[merc_index].MercUnk05 = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
mdus->MercData[merc_index].MercUnk05 = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||||
merc_index++;
|
merc_index++;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit the active merc slot first — the client marks the first entry
|
||||||
|
// in the list with the X (active marker), so order matters.
|
||||||
|
if (GetMercSlot() < max_slots && GetMercInfo().mercid != 0) {
|
||||||
|
fillMercEntry(GetMercSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then emit remaining owned mercs in slot order
|
||||||
|
for (int slot = 0; slot < max_slots; slot++) {
|
||||||
|
if (slot == GetMercSlot()) {
|
||||||
|
continue; // already emitted
|
||||||
|
}
|
||||||
|
fillMercEntry(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update count in case we skipped any invalid entries
|
// Update count in case we skipped any invalid entries
|
||||||
|
|||||||
@ -305,6 +305,7 @@ void MapOpcodes()
|
|||||||
ConnectedOpcodes[OP_MercenaryDismiss] = &Client::Handle_OP_MercenaryDismiss;
|
ConnectedOpcodes[OP_MercenaryDismiss] = &Client::Handle_OP_MercenaryDismiss;
|
||||||
ConnectedOpcodes[OP_MercenaryHire] = &Client::Handle_OP_MercenaryHire;
|
ConnectedOpcodes[OP_MercenaryHire] = &Client::Handle_OP_MercenaryHire;
|
||||||
ConnectedOpcodes[OP_MercenarySuspendRequest] = &Client::Handle_OP_MercenarySuspendRequest;
|
ConnectedOpcodes[OP_MercenarySuspendRequest] = &Client::Handle_OP_MercenarySuspendRequest;
|
||||||
|
ConnectedOpcodes[OP_MercenarySwitch] = &Client::Handle_OP_MercenarySwitch;
|
||||||
ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest;
|
ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest;
|
||||||
ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin;
|
ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin;
|
||||||
ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem;
|
ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem;
|
||||||
@ -10430,7 +10431,7 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app)
|
|||||||
}
|
}
|
||||||
|
|
||||||
MercenaryCommand_Struct* mc = (MercenaryCommand_Struct*)app->pBuffer;
|
MercenaryCommand_Struct* mc = (MercenaryCommand_Struct*)app->pBuffer;
|
||||||
uint32 merc_command = mc->MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (dismiss merc), 5 (normal state), 20 (unknown), 36 (zone in with merc)
|
uint32 merc_command = mc->MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (stance/state update), 5 (normal state), 20 (unknown), 36 (zone in with merc)
|
||||||
int32 option = mc->Option; // Seen -1 (zone in with no merc), 0 (setting to passive stance), 1 (normal or setting to balanced stance)
|
int32 option = mc->Option; // Seen -1 (zone in with no merc), 0 (setting to passive stance), 1 (normal or setting to balanced stance)
|
||||||
|
|
||||||
Log(Logs::General, Logs::Mercenaries, "Command %i, Option %i received from %s.", merc_command, option, GetName());
|
Log(Logs::General, Logs::Mercenaries, "Command %i, Option %i received from %s.", merc_command, option, GetName());
|
||||||
@ -10438,9 +10439,6 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app)
|
|||||||
if (!RuleB(Mercs, AllowMercs))
|
if (!RuleB(Mercs, AllowMercs))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Handle the Command here...
|
|
||||||
// Will need a list of what every type of command is supposed to do
|
|
||||||
// Unsure if there is a server response to this packet
|
|
||||||
if (option >= 0)
|
if (option >= 0)
|
||||||
{
|
{
|
||||||
Merc* merc = GetMerc();
|
Merc* merc = GetMerc();
|
||||||
@ -10496,14 +10494,14 @@ void Client::Handle_OP_MercenaryDataRequest(const EQApplicationPacket *app)
|
|||||||
if (merchant_id == 0) {
|
if (merchant_id == 0) {
|
||||||
|
|
||||||
//send info about your current merc(s)
|
//send info about your current merc(s)
|
||||||
if (GetMercInfo().mercid)
|
if (GetNumberOfMercenaries() > 0)
|
||||||
{
|
{
|
||||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Request for %s.", GetName());
|
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Request for %s.", GetName());
|
||||||
SendMercPersonalInfo();
|
SendMercPersonalInfo();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Not Sent - MercID (%i) for %s.", GetMercInfo().mercid, GetName());
|
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Not Sent - no mercs owned for %s.", GetName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10761,6 +10759,88 @@ void Client::Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app)
|
|||||||
SuspendMercCommand();
|
SuspendMercCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::Handle_OP_MercenarySwitch(const EQApplicationPacket *app)
|
||||||
|
{
|
||||||
|
if (app->size != sizeof(SwitchMercenary_Struct)) {
|
||||||
|
LogDebug("Size mismatch in OP_MercenarySwitch expected [{}] got [{}]", sizeof(SwitchMercenary_Struct), app->size);
|
||||||
|
DumpPacket(app);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RuleB(Mercs, AllowMercs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SwitchMercenary_Struct* sm = (SwitchMercenary_Struct*)app->pBuffer;
|
||||||
|
uint32 merc_ui_index = sm->MercIndex;
|
||||||
|
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Switch request to UI index %u received from %s.", merc_ui_index, GetName());
|
||||||
|
|
||||||
|
// The client sends a dense UI index (0, 1, 2...) that corresponds to the Nth
|
||||||
|
// owned merc in the list, matching the order sent by SendMercPersonalInfo().
|
||||||
|
// SendMercPersonalInfo emits the active slot first, then remaining slots in order.
|
||||||
|
// We must replicate that same ordering to map UI index -> internal slot.
|
||||||
|
int target_slot = -1;
|
||||||
|
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||||
|
uint32 ui_pos = 0;
|
||||||
|
|
||||||
|
// First: the active merc slot (emitted first in the packet)
|
||||||
|
if (GetMercSlot() < max_slots && m_mercinfo[GetMercSlot()].mercid != 0) {
|
||||||
|
if (ui_pos == merc_ui_index) {
|
||||||
|
target_slot = GetMercSlot();
|
||||||
|
}
|
||||||
|
ui_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then: remaining slots in order (skipping active slot)
|
||||||
|
if (target_slot < 0) {
|
||||||
|
for (int slot = 0; slot < max_slots; slot++) {
|
||||||
|
if (slot == GetMercSlot()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (m_mercinfo[slot].mercid != 0) {
|
||||||
|
if (ui_pos == merc_ui_index) {
|
||||||
|
target_slot = slot;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ui_pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_slot < 0) {
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Switch request denied — UI index %u has no corresponding merc for %s.", merc_ui_index, GetName());
|
||||||
|
SendMercResponsePackets(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_slot == GetMercSlot()) {
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Switch request ignored — already on slot %i for %s.", target_slot, GetName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend the currently active merc if one is spawned
|
||||||
|
Merc* current_merc = GetMerc();
|
||||||
|
if (current_merc) {
|
||||||
|
current_merc->Suspend();
|
||||||
|
// Clear merc pointer without wiping slot data (SetMerc(nullptr) would zero the slot)
|
||||||
|
current_merc->SetOwnerID(0);
|
||||||
|
SetMercID(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the suspend timer so the target merc can be unsuspended immediately.
|
||||||
|
// The cooldown is meant for rapid suspend/unsuspend of the same merc, not for switching.
|
||||||
|
if (!GetPTimers().Expired(&database, pTimerMercSuspend, false)) {
|
||||||
|
GetPTimers().Clear(&database, pTimerMercSuspend);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetMercSlot(static_cast<uint8>(target_slot));
|
||||||
|
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Switched active merc slot to %i (UI index %u) for %s.", target_slot, merc_ui_index, GetName());
|
||||||
|
|
||||||
|
// Unsuspend the target merc
|
||||||
|
SuspendMercCommand();
|
||||||
|
}
|
||||||
|
|
||||||
void Client::Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app)
|
void Client::Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app)
|
||||||
{
|
{
|
||||||
// The payload is 0 bytes.
|
// The payload is 0 bytes.
|
||||||
|
|||||||
@ -239,6 +239,7 @@
|
|||||||
void Handle_OP_MercenaryDismiss(const EQApplicationPacket *app);
|
void Handle_OP_MercenaryDismiss(const EQApplicationPacket *app);
|
||||||
void Handle_OP_MercenaryHire(const EQApplicationPacket *app);
|
void Handle_OP_MercenaryHire(const EQApplicationPacket *app);
|
||||||
void Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app);
|
void Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app);
|
||||||
|
void Handle_OP_MercenarySwitch(const EQApplicationPacket *app);
|
||||||
void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app);
|
void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app);
|
||||||
void Handle_OP_MoveCoin(const EQApplicationPacket *app);
|
void Handle_OP_MoveCoin(const EQApplicationPacket *app);
|
||||||
void Handle_OP_MoveItem(const EQApplicationPacket *app);
|
void Handle_OP_MoveItem(const EQApplicationPacket *app);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user