Mercenaires : double liberation du Group, et bots congedies par le balayage d inactivite

Double liberation
-----------------
Group::Disband() declenche sScriptMgr->OnGroupDisband(this) en toute premiere
instruction et se termine par sGroupMgr->RemoveGroup(this); delete this;

MercenaryMgr::OnGroupDisband congediait aussitot, donc ReleaseBot appelait
bot->RemoveFromGroup(), donc Group::RemoveMember, qui disband le groupe des
qu il repasse sous deux membres : un second Disband() imbrique faisait
delete this, puis le Disband() exterieur reprenait la main sur un objet
libere et le liberait une seconde fois. Le garde-fou m_releasing ne protegeait
que nos propres hooks, pas la reentrance du core.

Le tas s en trouvait corrompu et le serveur tombait bien plus tard, dans un
malloc sans rapport (Object::_InitValues sur un chargement de grille). Piege
pose avec MALLOC_CONF=junk:true,redzone:true,quarantine:...,abort:true, qui a
signale le bloc passe deux fois par le chemin de liberation.

Les deux hooks de groupe se contentent desormais de marquer les contrats
(pendingRelease) ; Update() les libere au tick suivant, hors de la pile
d appel du core.

Balayage d inactivite
---------------------
PlayerBotMgr::UpdateIdleBotLogout exemptait les bots du siege des capitales
mais pas les mercenaires : un bot loue, immobile aupres de son employeur,
etait juge inactif et deconnecte au bout de pbotbg_idlelogout secondes.
Exemption ajoutee via MercenaryMgr::IsAccountHired().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
SylvaniaCore deploy
2026-08-16 08:23:16 +02:00
parent 3bdd7fd4e8
commit 0dbbb73a24
3 changed files with 47 additions and 13 deletions
+34 -11
View File
@@ -428,15 +428,23 @@ void MercenaryMgr::DismissAll(ObjectGuid ownerGuid)
// Un membre a quitte un groupe, de son plein gre ou expulse. Si c est un // Un membre a quitte un groupe, de son plein gre ou expulse. Si c est un
// mercenaire, son contrat s arrete la ; si c est son employeur, toute sa // mercenaire, son contrat s arrete la ; si c est son employeur, toute sa
// compagnie se dissout. // compagnie se dissout.
//
// On se contente de MARQUER les contrats, la liberation a lieu au tick suivant.
// Ce hook est appele depuis Group::RemoveMember et depuis Group::Disband ;
// congedier sur-le-champ rappelle Group::RemoveMember par en dessous, or
// RemoveMember disband le groupe des qu il repasse sous deux membres et
// Disband() se termine par `delete this`. L appel exterieur reprenait alors la
// main sur un Group libere et le liberait une seconde fois -- double liberation
// qui corrompait le tas et faisait tomber le serveur bien plus tard, dans un
// malloc sans rapport.
void MercenaryMgr::OnPlayerLeftGroup(ObjectGuid guid) void MercenaryMgr::OnPlayerLeftGroup(ObjectGuid guid)
{ {
if (m_releasing) if (m_releasing)
return; return;
if (IsMercenary(guid)) for (MercenaryContract& contract : m_contracts)
DismissOne(guid); if (contract.botGuid == guid || contract.ownerGuid == guid)
else contract.pendingRelease = true;
DismissAll(guid);
} }
void MercenaryMgr::OnGroupDisband(Group* group) void MercenaryMgr::OnGroupDisband(Group* group)
@@ -444,13 +452,12 @@ void MercenaryMgr::OnGroupDisband(Group* group)
if (m_releasing || !group || m_contracts.empty()) if (m_releasing || !group || m_contracts.empty())
return; return;
Group::MemberSlotList const& members = group->GetMemberSlots(); // Meme regle que ci-dessus : ne rien toucher pendant que le core demonte le
std::vector<ObjectGuid> guids; // groupe. Disband() retire lui-meme tous les membres juste apres ce hook.
for (Group::MemberSlot const& slot : members) for (Group::MemberSlot const& slot : group->GetMemberSlots())
guids.push_back(slot.guid); for (MercenaryContract& contract : m_contracts)
if (contract.botGuid == slot.guid || contract.ownerGuid == slot.guid)
for (ObjectGuid const& guid : guids) contract.pendingRelease = true;
OnPlayerLeftGroup(guid);
} }
void MercenaryMgr::OnPlayerLogout(Player* player) void MercenaryMgr::OnPlayerLogout(Player* player)
@@ -473,6 +480,22 @@ void MercenaryMgr::Update(uint32 diff)
for (std::vector<MercenaryContract>::iterator it = m_contracts.begin(); it != m_contracts.end(); ) for (std::vector<MercenaryContract>::iterator it = m_contracts.begin(); it != m_contracts.end(); )
{ {
// Contrat rompu par un hook de groupe : la liberation a ete differee
// jusqu ici pour ne pas reentrer dans Group::RemoveMember / Disband().
if (it->pendingRelease)
{
MercenaryContract const contract = *it;
it = m_contracts.erase(it);
if (Player* owner = ObjectAccessor::FindConnectedPlayer(contract.ownerGuid))
ChatHandler(owner->GetSession()).PSendSysMessage(
"|cff00ff00[Portail]|r Votre %s retourne d'où il vient. Il vous faudra payer de nouveau pour en invoquer un autre.",
GetRoleName(contract.role));
ReleaseBot(contract);
continue;
}
// L employeur d abord : plus de maitre, plus de contrat. C est le filet // L employeur d abord : plus de maitre, plus de contrat. C est le filet
// de securite si un hook n a pas ete appele (crash client, timeout...). // de securite si un hook n a pas ete appele (crash client, timeout...).
Player* owner = ObjectAccessor::FindConnectedPlayer(it->ownerGuid); Player* owner = ObjectAccessor::FindConnectedPlayer(it->ownerGuid);
+6 -1
View File
@@ -63,7 +63,8 @@ enum MercenaryResult
struct MercenaryContract struct MercenaryContract
{ {
MercenaryContract() : accountId(0), role(0), stage(MERC_STAGE_SUMMONING), waitSeconds(0) { } MercenaryContract() : accountId(0), role(0), stage(MERC_STAGE_SUMMONING), waitSeconds(0),
pendingRelease(false) { }
uint32 accountId; // compte bot reserve uint32 accountId; // compte bot reserve
ObjectGuid ownerGuid; // joueur qui a paye ObjectGuid ownerGuid; // joueur qui a paye
@@ -71,6 +72,10 @@ struct MercenaryContract
uint8 role; // ROLE_TANK, ROLE_HEALER ou ROLE_DAMAGE uint8 role; // ROLE_TANK, ROLE_HEALER ou ROLE_DAMAGE
uint8 stage; uint8 stage;
uint32 waitSeconds; uint32 waitSeconds;
// Contrat rompu par un hook de groupe du core, mais dont la liberation est
// differee au prochain Update() : voir MercenaryMgr::OnPlayerLeftGroup().
bool pendingRelease;
}; };
class TC_GAME_API MercenaryMgr class TC_GAME_API MercenaryMgr
+7 -1
View File
@@ -31,6 +31,7 @@
#include "LFGMgr.h" #include "LFGMgr.h"
#include "Config.h" #include "Config.h"
#include "PlayerBotSession.h" #include "PlayerBotSession.h"
#include "MercenaryMgr.h"
#include "AccountMgr.h" #include "AccountMgr.h"
#include "BattlenetAccountMgr.h" #include "BattlenetAccountMgr.h"
#include "CharacterPackets.h" #include "CharacterPackets.h"
@@ -3515,7 +3516,12 @@ void PlayerBotMgr::UpdateIdleBotLogout()
bool inSiege = false; bool inSiege = false;
if (BotBGAI* botAI = dynamic_cast<BotBGAI*>(player->GetAI())) if (BotBGAI* botAI = dynamic_cast<BotBGAI*>(player->GetAI()))
inSiege = botAI->IsSiegeMode(); inSiege = botAI->IsSiegeMode();
bool busy = inSiege || pSession->HasSchedules() || player->InBattleground() || player->InArena() // Module Mercenaires : un bot loue est sous contrat meme s il reste
// immobile aupres de son employeur. Sans cette exemption le balayage
// d inactivite le deconnectait au bout de pbotbg_idlelogout secondes et
// le mercenaire disparaissait sans explication.
bool const hired = sMercenaryMgr->IsAccountHired(accId);
bool busy = inSiege || hired || pSession->HasSchedules() || player->InBattleground() || player->InArena()
|| player->InBattlegroundQueue() || player->GetMap()->IsDungeon() || player->isUsingLfg() || player->InBattlegroundQueue() || player->GetMap()->IsDungeon() || player->isUsingLfg()
|| player->IsInCombat(); || player->IsInCombat();
if (busy) if (busy)