mirror of
https://github.com/EQEmu/Server.git
synced 2026-04-20 01:12:36 +00:00
Merge 804447a6d0eec0ca18ebd123e6e46cdb3c79d022 into ba2ca5eada80ac758ef8c2a2510cb39b0563119a
This commit is contained in:
commit
8d2130230e
@ -376,6 +376,7 @@ N(OP_MercenaryDismiss),
|
||||
N(OP_MercenaryHire),
|
||||
N(OP_MercenarySuspendRequest),
|
||||
N(OP_MercenarySuspendResponse),
|
||||
N(OP_MercenarySwitch),
|
||||
N(OP_MercenaryTimer),
|
||||
N(OP_MercenaryTimerRequest),
|
||||
N(OP_MercenaryUnknown1),
|
||||
|
||||
@ -6236,6 +6236,12 @@ struct SuspendMercenary_Struct {
|
||||
/*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]
|
||||
// Response to suspend merc with timestamp
|
||||
struct SuspendMercenaryResponse_Struct {
|
||||
|
||||
@ -2278,15 +2278,19 @@ namespace RoF2
|
||||
// There are 2 different sized versions of this packet depending if a merc is hired or not
|
||||
if (emu->MercStatus >= 0)
|
||||
{
|
||||
PacketSize += sizeof(structs::MercenaryDataUpdate_Struct) + (sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount;
|
||||
|
||||
// Per-merc size: base struct minus Stances[1] and MercUnk05,
|
||||
// then add back actual stances and name length per merc.
|
||||
// MercUnk05 is a single trailing field after all mercs.
|
||||
PacketSize += sizeof(structs::MercenaryDataUpdate_Struct);
|
||||
uint32 r;
|
||||
uint32 k;
|
||||
for (r = 0; r < emu->MercCount; r++)
|
||||
{
|
||||
PacketSize += sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct) - sizeof(uint32); // subtract Stances[1] and MercUnk05
|
||||
PacketSize += sizeof(structs::MercenaryStance_Struct) * emu->MercData[r].StanceCount;
|
||||
PacketSize += strlen(emu->MercData[r].MercName); // Null Terminator size already accounted for in the struct
|
||||
}
|
||||
PacketSize += sizeof(uint32); // MercUnk05 - trailing field after all mercs
|
||||
|
||||
outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, PacketSize);
|
||||
Buffer = (char *)outapp->pBuffer;
|
||||
@ -2312,15 +2316,14 @@ namespace RoF2
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].StanceCount);
|
||||
VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercData[r].MercUnk03);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->MercData[r].MercUnk04);
|
||||
//VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // MercName
|
||||
VARSTRUCT_ENCODE_STRING(Buffer, emu->MercData[r].MercName);
|
||||
for (k = 0; k < emu->MercData[r].StanceCount; k++)
|
||||
{
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].StanceIndex);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance);
|
||||
}
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // MercUnk05
|
||||
}
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[0].MercUnk05); // MercUnk05 - trailing field (unlocked slot count)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -255,6 +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_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, MaxMercSlots, 6, "Maximum number of mercenary slots per character (max 6)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Guild)
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit b9aeec6eaf3d5610503439b4fae3581d9aff08e8
|
||||
Subproject commit 4dfe1be74e684acca19ac1cf96cce0df9eac2a2d
|
||||
@ -418,6 +418,7 @@ OP_MercenaryUnknown1=0x5d26
|
||||
OP_MercenaryCommand=0x27f2
|
||||
OP_MercenarySuspendRequest=0x4407
|
||||
OP_MercenarySuspendResponse=0x6f03
|
||||
OP_MercenarySwitch=0x1b37
|
||||
OP_MercenaryUnsuspendResponse=0x27a0
|
||||
|
||||
# Looting
|
||||
|
||||
166
zone/client.cpp
166
zone/client.cpp
@ -1037,7 +1037,7 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
}
|
||||
|
||||
if (dead || (!GetMerc() && !GetMercInfo().IsSuspended)) {
|
||||
memset(&m_mercinfo, 0, sizeof(struct MercInfo));
|
||||
memset(&m_mercinfo, 0, sizeof(m_mercinfo));
|
||||
}
|
||||
|
||||
m_pp.lastlogin = time(nullptr);
|
||||
@ -7940,75 +7940,125 @@ void Client::SendWebLink(const char *website)
|
||||
|
||||
void Client::SendMercPersonalInfo()
|
||||
{
|
||||
uint32 mercTypeCount = 1;
|
||||
uint32 mercCount = 1; //TODO: Un-hardcode this and support multiple mercs like in later clients than SoD.
|
||||
uint32 i = 0;
|
||||
uint32 altCurrentType = 19; //TODO: Implement alternate currency purchases involving mercs!
|
||||
|
||||
MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID];
|
||||
|
||||
int stancecount = 0;
|
||||
stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size();
|
||||
if(stancecount > MAX_MERC_STANCES || mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES)
|
||||
{
|
||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo canceled: (%i) (%i) (%i) for %s", stancecount, mercCount, mercTypeCount, GetName());
|
||||
SendMercMerchantResponsePacket(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClientVersion() >= EQ::versions::ClientVersion::RoF) {
|
||||
auto outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct));
|
||||
auto mdus = (MercenaryDataUpdate_Struct *) outapp->pBuffer;
|
||||
|
||||
mdus->MercStatus = 0;
|
||||
mdus->MercCount = mercCount;
|
||||
mdus->MercData[i].MercID = mercData->MercTemplateID;
|
||||
mdus->MercData[i].MercType = mercData->MercType;
|
||||
mdus->MercData[i].MercSubType = mercData->MercSubType;
|
||||
mdus->MercData[i].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0);
|
||||
mdus->MercData[i].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0);
|
||||
mdus->MercData[i].Status = 0;
|
||||
mdus->MercData[i].AltCurrencyCost = Merc::CalcPurchaseCost(
|
||||
mercData->MercTemplateID,
|
||||
GetLevel(),
|
||||
altCurrentType
|
||||
);
|
||||
mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(
|
||||
mercData->MercTemplateID,
|
||||
GetLevel(),
|
||||
altCurrentType
|
||||
);
|
||||
mdus->MercData[i].AltCurrencyType = altCurrentType;
|
||||
mdus->MercData[i].MercUnk01 = 0;
|
||||
mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime();
|
||||
mdus->MercData[i].MerchantSlot = i + 1;
|
||||
mdus->MercData[i].MercUnk02 = 1;
|
||||
mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size();
|
||||
mdus->MercData[i].MercUnk03 = 0;
|
||||
mdus->MercData[i].MercUnk04 = 1;
|
||||
|
||||
strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name, sizeof(mdus->MercData[i].MercName));
|
||||
|
||||
uint32 stanceindex = 0;
|
||||
if (mdus->MercData[i].StanceCount != 0) {
|
||||
auto iter = zone->merc_stance_list[mercData->MercTemplateID].begin();
|
||||
while (iter != zone->merc_stance_list[mercData->MercTemplateID].end()) {
|
||||
mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex;
|
||||
mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID);
|
||||
stanceindex++;
|
||||
++iter;
|
||||
}
|
||||
// Count owned mercs across all slots
|
||||
uint32 mercCount = GetNumberOfMercenaries();
|
||||
if (mercCount == 0) {
|
||||
SendClearMercInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
mdus->MercData[i].MercUnk05 = 1;
|
||||
uint32 packetSize = sizeof(MercenaryDataUpdate_Struct) + (mercCount * sizeof(MercenaryData_Struct));
|
||||
auto outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, packetSize);
|
||||
memset(outapp->pBuffer, 0, packetSize);
|
||||
auto mdus = (MercenaryDataUpdate_Struct *) outapp->pBuffer;
|
||||
|
||||
mdus->MercStatus = 0;
|
||||
mdus->MercCount = mercCount;
|
||||
|
||||
// Lambda to populate a single merc entry in the packet
|
||||
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||
uint32 merc_index = 0;
|
||||
|
||||
auto fillMercEntry = [&](int slot) {
|
||||
auto& info = GetMercInfo(slot);
|
||||
if (info.mercid == 0 || merc_index >= MAX_MERC) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto tmpl_it = zone->merc_templates.find(info.MercTemplateID);
|
||||
if (tmpl_it == zone->merc_templates.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MercTemplate *mercData = &tmpl_it->second;
|
||||
uint32 stancecount = 0;
|
||||
auto stance_it = zone->merc_stance_list.find(mercData->MercTemplateID);
|
||||
if (stance_it != zone->merc_stance_list.end()) {
|
||||
stancecount = stance_it->second.size();
|
||||
}
|
||||
|
||||
if (stancecount > MAX_MERC_STANCES) {
|
||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo: stance count %u exceeds max for slot %i, skipping", stancecount, slot);
|
||||
return;
|
||||
}
|
||||
|
||||
mdus->MercData[merc_index].MercID = mercData->MercTemplateID;
|
||||
mdus->MercData[merc_index].MercType = mercData->MercType;
|
||||
mdus->MercData[merc_index].MercSubType = mercData->MercSubType;
|
||||
mdus->MercData[merc_index].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0);
|
||||
mdus->MercData[merc_index].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0);
|
||||
mdus->MercData[merc_index].Status = info.IsSuspended ? 0 : 1;
|
||||
mdus->MercData[merc_index].AltCurrencyCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType);
|
||||
mdus->MercData[merc_index].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType);
|
||||
mdus->MercData[merc_index].AltCurrencyType = altCurrentType;
|
||||
mdus->MercData[merc_index].MercUnk01 = 0;
|
||||
mdus->MercData[merc_index].TimeLeft = info.MercTimerRemaining;
|
||||
mdus->MercData[merc_index].MerchantSlot = merc_index + 1;
|
||||
mdus->MercData[merc_index].MercUnk02 = (slot == GetMercSlot()) ? 1 : 0;
|
||||
mdus->MercData[merc_index].StanceCount = stancecount;
|
||||
mdus->MercData[merc_index].MercUnk03 = 0;
|
||||
mdus->MercData[merc_index].MercUnk04 = 1;
|
||||
|
||||
strn0cpy(mdus->MercData[merc_index].MercName, info.merc_name, sizeof(mdus->MercData[merc_index].MercName));
|
||||
|
||||
uint32 stanceindex = 0;
|
||||
if (stance_it != zone->merc_stance_list.end()) {
|
||||
for (const auto& stance : stance_it->second) {
|
||||
mdus->MercData[merc_index].Stances[stanceindex].StanceIndex = stanceindex;
|
||||
mdus->MercData[merc_index].Stances[stanceindex].Stance = stance.StanceID;
|
||||
stanceindex++;
|
||||
}
|
||||
}
|
||||
|
||||
mdus->MercData[merc_index].MercUnk05 = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||
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
|
||||
mdus->MercCount = merc_index;
|
||||
|
||||
FastQueuePacket(&outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
} else {
|
||||
// Pre-RoF path (SoD and earlier) — single merc only
|
||||
if (GetMercInfo().MercTemplateID == 0) {
|
||||
SendClearMercInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID);
|
||||
if (tmpl_it == zone->merc_templates.end()) {
|
||||
SendClearMercInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
MercTemplate *mercData = &tmpl_it->second;
|
||||
uint32 mercTypeCount = 1;
|
||||
uint32 mercCount = 1;
|
||||
uint32 i = 0;
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct));
|
||||
auto mml = (MercenaryMerchantList_Struct *) outapp->pBuffer;
|
||||
|
||||
mml->MercTypeCount = mercTypeCount; //We should only have one merc entry.
|
||||
mml->MercTypeCount = mercTypeCount;
|
||||
mml->MercGrades[i] = 1;
|
||||
|
||||
mml->MercCount = mercCount;
|
||||
|
||||
@ -1773,6 +1773,7 @@ public:
|
||||
MercInfo& GetMercInfo(uint8 slot) { return m_mercinfo[slot]; }
|
||||
MercInfo& GetMercInfo() { return m_mercinfo[mercSlot]; }
|
||||
uint8 GetNumberOfMercenaries();
|
||||
int GetFirstFreeMercSlot();
|
||||
void SetMerc(Merc* newmerc);
|
||||
void SendMercResponsePackets(uint32 ResponseType);
|
||||
void SendMercMerchantResponsePacket(int32 response_type);
|
||||
|
||||
@ -305,6 +305,7 @@ void MapOpcodes()
|
||||
ConnectedOpcodes[OP_MercenaryDismiss] = &Client::Handle_OP_MercenaryDismiss;
|
||||
ConnectedOpcodes[OP_MercenaryHire] = &Client::Handle_OP_MercenaryHire;
|
||||
ConnectedOpcodes[OP_MercenarySuspendRequest] = &Client::Handle_OP_MercenarySuspendRequest;
|
||||
ConnectedOpcodes[OP_MercenarySwitch] = &Client::Handle_OP_MercenarySwitch;
|
||||
ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest;
|
||||
ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin;
|
||||
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;
|
||||
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)
|
||||
|
||||
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))
|
||||
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)
|
||||
{
|
||||
Merc* merc = GetMerc();
|
||||
@ -10496,14 +10494,14 @@ void Client::Handle_OP_MercenaryDataRequest(const EQApplicationPacket *app)
|
||||
if (merchant_id == 0) {
|
||||
|
||||
//send info about your current merc(s)
|
||||
if (GetMercInfo().mercid)
|
||||
if (GetNumberOfMercenaries() > 0)
|
||||
{
|
||||
Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Request for %s.", GetName());
|
||||
SendMercPersonalInfo();
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -10687,6 +10685,21 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app)
|
||||
return;
|
||||
}
|
||||
|
||||
// Suspend active merc if one exists before hiring into a new slot
|
||||
Merc* current_merc = GetMerc();
|
||||
if (current_merc) {
|
||||
current_merc->Suspend();
|
||||
SetMerc(nullptr);
|
||||
}
|
||||
|
||||
// Select a free slot for the new hire
|
||||
int free_slot = GetFirstFreeMercSlot();
|
||||
if (free_slot < 0) {
|
||||
SendMercResponsePackets(6);
|
||||
return;
|
||||
}
|
||||
SetMercSlot(static_cast<uint8>(free_slot));
|
||||
|
||||
// Set time remaining to max on Hire
|
||||
GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS);
|
||||
|
||||
@ -10706,6 +10719,10 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app)
|
||||
|
||||
// approved hire request
|
||||
SendMercMerchantResponsePacket(0);
|
||||
|
||||
// Update the client's Manage tab with the newly hired merc info
|
||||
SendMercPersonalInfo();
|
||||
SendMercTimer(merc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -10742,6 +10759,88 @@ void Client::Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app)
|
||||
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)
|
||||
{
|
||||
// The payload is 0 bytes.
|
||||
|
||||
@ -239,6 +239,7 @@
|
||||
void Handle_OP_MercenaryDismiss(const EQApplicationPacket *app);
|
||||
void Handle_OP_MercenaryHire(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_MoveCoin(const EQApplicationPacket *app);
|
||||
void Handle_OP_MoveItem(const EQApplicationPacket *app);
|
||||
|
||||
105
zone/merc.cpp
105
zone/merc.cpp
@ -4885,14 +4885,8 @@ bool Client::CheckCanHireMerc(Mob* merchant, uint32 template_id) {
|
||||
|
||||
MercTemplate* mercTemplate = zone->GetMercTemplate(template_id);
|
||||
|
||||
//check for suspended merc
|
||||
if(GetMercInfo().mercid != 0 && GetMercInfo().IsSuspended) {
|
||||
SendMercResponsePackets(6);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if max number of mercs is already reached
|
||||
if(GetNumberOfMercenaries() >= MAXMERCS) {
|
||||
// Check if all merc slots are full (counts both active and suspended mercs)
|
||||
if (GetFirstFreeMercSlot() < 0) {
|
||||
SendMercResponsePackets(6);
|
||||
return false;
|
||||
}
|
||||
@ -5046,8 +5040,21 @@ void Client::SuspendMercCommand() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suspend any currently active merc before unsuspending this one
|
||||
Merc* active_merc = GetMerc();
|
||||
if (active_merc) {
|
||||
active_merc->Suspend();
|
||||
SetMerc(nullptr);
|
||||
}
|
||||
|
||||
// Get merc, assign it to client & spawn
|
||||
Merc* merc = Merc::LoadMercenary(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true);
|
||||
auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID);
|
||||
if (tmpl_it == zone->merc_templates.end()) {
|
||||
SendMercResponsePackets(3);
|
||||
Log(Logs::General, Logs::Mercenaries, "SuspendMercCommand Invalid template for %s.", GetName());
|
||||
return;
|
||||
}
|
||||
Merc* merc = Merc::LoadMercenary(this, &tmpl_it->second, 0, true);
|
||||
if(merc)
|
||||
{
|
||||
SpawnMerc(merc, false);
|
||||
@ -5119,40 +5126,56 @@ void Client::SpawnMercOnZone() {
|
||||
|
||||
if(database.LoadMercenaryInfo(this))
|
||||
{
|
||||
if(!GetMercInfo().IsSuspended)
|
||||
{
|
||||
// Find the active (non-suspended) merc slot, or fall back to the first owned slot
|
||||
int active_slot = -1;
|
||||
int first_owned_slot = -1;
|
||||
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||
for (int slot = 0; slot < max_slots; slot++) {
|
||||
if (GetMercInfo(slot).mercid != 0) {
|
||||
if (first_owned_slot < 0) {
|
||||
first_owned_slot = slot;
|
||||
}
|
||||
if (!GetMercInfo(slot).IsSuspended) {
|
||||
active_slot = slot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active_slot >= 0) {
|
||||
SetMercSlot(static_cast<uint8>(active_slot));
|
||||
GetMercInfo().SuspendedTime = 0;
|
||||
// Get merc, assign it to client & spawn
|
||||
Merc* merc = Merc::LoadMercenary(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true);
|
||||
if(merc)
|
||||
{
|
||||
SpawnMerc(merc, false);
|
||||
auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID);
|
||||
if (tmpl_it != zone->merc_templates.end()) {
|
||||
Merc* merc = Merc::LoadMercenary(this, &tmpl_it->second, 0, true);
|
||||
if (merc) {
|
||||
SpawnMerc(merc, false);
|
||||
}
|
||||
}
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Normal Merc for %s.", GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Normal Merc (slot %i) for %s.", active_slot, GetName());
|
||||
} else if (first_owned_slot >= 0) {
|
||||
SetMercSlot(static_cast<uint8>(first_owned_slot));
|
||||
int32 TimeDiff = GetMercInfo().SuspendedTime - time(nullptr);
|
||||
if (TimeDiff > 0)
|
||||
{
|
||||
if (!GetPTimers().Enabled(pTimerMercSuspend))
|
||||
{
|
||||
// Start the timer to send the packet that refreshes the Unsuspend Button
|
||||
if (TimeDiff > 0) {
|
||||
if (!GetPTimers().Enabled(pTimerMercSuspend)) {
|
||||
GetPTimers().Start(pTimerMercSuspend, TimeDiff);
|
||||
}
|
||||
}
|
||||
// Send Mercenary Status/Timer packet
|
||||
SendMercTimer(GetMerc());
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Suspended Merc (slot %i) for %s.", first_owned_slot, GetName());
|
||||
} else {
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone No valid merc slots found for %s.", GetName());
|
||||
}
|
||||
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Suspended Merc for %s.", GetName());
|
||||
// Send merc personal info for all owned mercs (populates the Manage tab)
|
||||
if (GetNumberOfMercenaries() > 0) {
|
||||
SendMercPersonalInfo();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No Merc Hired
|
||||
// RoF+ displays a message from the following packet, which seems useless
|
||||
//SendClearMercInfo();
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Failed to load Merc Info from the Database for %s.", GetName());
|
||||
Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone No merc info in database for %s.", GetName());
|
||||
}
|
||||
}
|
||||
|
||||
@ -5323,9 +5346,18 @@ bool Client::DismissMerc(uint32 MercID) {
|
||||
GetMerc()->Depop();
|
||||
}
|
||||
|
||||
SendClearMercInfo();
|
||||
// Clear the dismissed merc's slot data so it becomes available
|
||||
memset(&GetMercInfo(), 0, sizeof(MercInfo));
|
||||
|
||||
SetMerc(nullptr);
|
||||
|
||||
// Update the client with remaining mercs or clear if none left
|
||||
if (GetNumberOfMercenaries() > 0) {
|
||||
SendMercPersonalInfo();
|
||||
} else {
|
||||
SendClearMercInfo();
|
||||
}
|
||||
|
||||
return Dismissed;
|
||||
}
|
||||
|
||||
@ -5579,6 +5611,17 @@ uint8 Client::GetNumberOfMercenaries()
|
||||
return count;
|
||||
}
|
||||
|
||||
int Client::GetFirstFreeMercSlot()
|
||||
{
|
||||
int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS);
|
||||
for (int slot_id = 0; slot_id < max_slots; slot_id++) {
|
||||
if (m_mercinfo[slot_id].mercid == 0) {
|
||||
return slot_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Merc::SetMercData( uint32 template_id ) {
|
||||
|
||||
MercTemplate* merc_template = zone->GetMercTemplate(template_id);
|
||||
|
||||
@ -33,7 +33,7 @@ namespace EQ
|
||||
struct ItemData;
|
||||
}
|
||||
|
||||
#define MAXMERCS 1
|
||||
constexpr int MAXMERCS = 11;
|
||||
#define TANK 1
|
||||
#define HEALER 2
|
||||
#define MELEEDPS 9
|
||||
|
||||
@ -2225,7 +2225,7 @@ bool ZoneDatabase::LoadCurrentMercenary(Client* c)
|
||||
{
|
||||
const uint8 mercenary_slot = c->GetMercSlot();
|
||||
|
||||
if (mercenary_slot > MAXMERCS) {
|
||||
if (mercenary_slot >= MAXMERCS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2277,7 +2277,7 @@ bool ZoneDatabase::SaveMercenary(Merc* m)
|
||||
auto e = MercsRepository::NewEntity();
|
||||
|
||||
e.OwnerCharacterID = m->GetMercenaryCharacterID();
|
||||
e.Slot = (c->GetNumberOfMercenaries() - 1);
|
||||
e.Slot = c->GetMercSlot();
|
||||
e.Name = m->GetCleanName();
|
||||
e.TemplateID = m->GetMercenaryTemplateID();
|
||||
e.SuspendedTime = c->GetMercInfo().SuspendedTime;
|
||||
@ -2318,7 +2318,7 @@ bool ZoneDatabase::SaveMercenary(Merc* m)
|
||||
auto e = MercsRepository::FindOne(*this, m->GetMercenaryID());
|
||||
|
||||
e.OwnerCharacterID = m->GetMercenaryCharacterID();
|
||||
e.Slot = (c->GetNumberOfMercenaries() - 1);
|
||||
e.Slot = c->GetMercSlot();
|
||||
e.Name = m->GetCleanName();
|
||||
e.TemplateID = m->GetMercenaryTemplateID();
|
||||
e.SuspendedTime = c->GetMercInfo().SuspendedTime;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user