From 0dbbb73a248f4952fa519f80f403531034651113 Mon Sep 17 00:00:00 2001 From: SylvaniaCore deploy Date: Sun, 16 Aug 2026 08:23:16 +0200 Subject: [PATCH] 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 --- src/server/game/Mercenary/MercenaryMgr.cpp | 45 ++++++++++++++++------ src/server/game/Mercenary/MercenaryMgr.h | 7 +++- src/server/game/PlayerBot/PlayerBotMgr.cpp | 8 +++- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/server/game/Mercenary/MercenaryMgr.cpp b/src/server/game/Mercenary/MercenaryMgr.cpp index 3adca0d..815c2c4 100644 --- a/src/server/game/Mercenary/MercenaryMgr.cpp +++ b/src/server/game/Mercenary/MercenaryMgr.cpp @@ -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 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::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); diff --git a/src/server/game/Mercenary/MercenaryMgr.h b/src/server/game/Mercenary/MercenaryMgr.h index baf760a..1baa56c 100644 --- a/src/server/game/Mercenary/MercenaryMgr.h +++ b/src/server/game/Mercenary/MercenaryMgr.h @@ -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 diff --git a/src/server/game/PlayerBot/PlayerBotMgr.cpp b/src/server/game/PlayerBot/PlayerBotMgr.cpp index 65e3de7..21d9700 100644 --- a/src/server/game/PlayerBot/PlayerBotMgr.cpp +++ b/src/server/game/PlayerBot/PlayerBotMgr.cpp @@ -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(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)