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
// mercenaire, son contrat s arrete la ; si c est son employeur, toute sa
// 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)
{
if (m_releasing)
return;
if (IsMercenary(guid))
DismissOne(guid);
else
DismissAll(guid);
for (MercenaryContract& contract : m_contracts)
if (contract.botGuid == guid || contract.ownerGuid == guid)
contract.pendingRelease = true;
}
void MercenaryMgr::OnGroupDisband(Group* group)
@@ -444,13 +452,12 @@ void MercenaryMgr::OnGroupDisband(Group* group)
if (m_releasing || !group || m_contracts.empty())
return;
Group::MemberSlotList const& members = group->GetMemberSlots();
std::vector<ObjectGuid> guids;
for (Group::MemberSlot const& slot : members)
guids.push_back(slot.guid);
for (ObjectGuid const& guid : guids)
OnPlayerLeftGroup(guid);
// Meme regle que ci-dessus : ne rien toucher pendant que le core demonte le
// groupe. Disband() retire lui-meme tous les membres juste apres ce hook.
for (Group::MemberSlot const& slot : group->GetMemberSlots())
for (MercenaryContract& contract : m_contracts)
if (contract.botGuid == slot.guid || contract.ownerGuid == slot.guid)
contract.pendingRelease = true;
}
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(); )
{
// 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
// de securite si un hook n a pas ete appele (crash client, timeout...).
Player* owner = ObjectAccessor::FindConnectedPlayer(it->ownerGuid);
+6 -1
View File
@@ -63,7 +63,8 @@ enum MercenaryResult
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
ObjectGuid ownerGuid; // joueur qui a paye
@@ -71,6 +72,10 @@ struct MercenaryContract
uint8 role; // ROLE_TANK, ROLE_HEALER ou ROLE_DAMAGE
uint8 stage;
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
+7 -1
View File
@@ -31,6 +31,7 @@
#include "LFGMgr.h"
#include "Config.h"
#include "PlayerBotSession.h"
#include "MercenaryMgr.h"
#include "AccountMgr.h"
#include "BattlenetAccountMgr.h"
#include "CharacterPackets.h"
@@ -3515,7 +3516,12 @@ void PlayerBotMgr::UpdateIdleBotLogout()
bool inSiege = false;
if (BotBGAI* botAI = dynamic_cast<BotBGAI*>(player->GetAI()))
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->IsInCombat();
if (busy)