Mercenaires : trois correctifs issus de l audit du module

1. La re-resolution des sorts passait par une file qui la jetait.

Le correctif 19c0cf03 poussait un BGSType_DelayLevelup apres la creation
de l IA de groupe. Mesure sur quatre mercenaires : deux repares, deux
non. En cause PushScheduleToQueue, qui jette silencieusement une tache
si une du meme type est deja en file, et -- pour ce type precisement --
si IsSettingFinish() est faux. La reparation ne partait qu au hasard.

On appelle desormais OnLevelupToBotAI() directement. L IA de groupe
vient d etre creee : le dynamic_cast qui l ouvre aboutit forcement.

2. Le raccourci de mise en place renvoyait des mercenaires nus.

Signale en jeu : « Kaerbrus n a meme pas d equipement ». Constate en
base : niveau 110, 76 sorts, zero piece portee.

ProcessSetting court-circuite le re-level d un bot deja au bon niveau,
avec ses talents et la bonne specialisation -- et saute du meme coup les
etapes d habillage. Un bot ayant perdu son equipement ne le retrouvait
jamais : a chaque embauche le raccourci concluait que tout allait bien.

On verifie donc qu il porte au moins dix pieces avant de couper.

3. Le rappel n intervenait qu au-dela de 160 metres.

Mesure : au moment d un « !attack », deux des quatre mercenaires se
trouvaient a 100 et 106 metres de leur employeur. Sous le seuil de
rappel, ils acceptaient l ordre et partaient A PIED -- une quinzaine de
secondes de marche, le combat fini avant leur arrivee. Vu du joueur, le
mercenaire « refuse d attaquer ».

Ce seuil convient a un bot de terrain, pas a un membre de groupe. Ramene
a deux fois la portee de recherche, soit 64 metres : bien au-dela des 32
ou l IA engage et des 28 de portee des sorts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BlaMacfly
2026-09-11 13:25:34 +02:00
parent 7f0c3a5453
commit 65d5978f4a
3 changed files with 85 additions and 10 deletions
@@ -264,9 +264,22 @@ void BotGroupAI::ProcessAttackCommand()
{
Unit* pMasterTarget = m_MasterPlayer->GetSelectedUnit();
if (!pMasterTarget || !pMasterTarget->IsAlive())
{
TC_LOG_ERROR("botai", "ATTACKDBG %s: ordre recu mais le maitre n a pas de cible vivante.",
me->GetName().c_str());
return;
}
if (!me->IsValidAttackTarget(pMasterTarget))
{
TC_LOG_ERROR("botai", "ATTACKDBG %s: cible %u (entree %u) refusee par IsValidAttackTarget.",
me->GetName().c_str(), pMasterTarget->GetGUID().GetCounter(),
pMasterTarget->ToCreature() ? pMasterTarget->ToCreature()->GetEntry() : 0);
return;
}
TC_LOG_ERROR("botai", "ATTACKDBG %s: cible %u (entree %u) retenue, distance %.1f.",
me->GetName().c_str(), pMasterTarget->GetGUID().GetCounter(),
pMasterTarget->ToCreature() ? pMasterTarget->ToCreature()->GetEntry() : 0,
me->GetDistance(pMasterTarget));
me->SetSelection(pMasterTarget->GetGUID());
m_ForceFlee = false;
m_StopFollow = false;
@@ -1448,7 +1461,26 @@ bool BotGroupAI::TryTeleportToMaster()
m_Flee.Clear();
return true;
}
else if (me->GetDistance(m_MasterPlayer->GetPosition()) > BOTAI_SEARCH_RANGE * 5)
// DISTANCE_DE_RAPPEL
//
// MESURE (audit du 09/09) : au moment d un 芦 !attack 禄, deux des quatre
// mercenaires se trouvaient a 100 et 106 metres de leur employeur, les
// deux autres a 21 et 28. Les lointains n etaient pas perdus au sens du
// code -- simplement sous le seuil de rappel, qui valait
// BOTAI_SEARCH_RANGE * 5, soit 160 metres.
//
// Ils recevaient donc l ordre, l acceptaient, et partaient A PIED. Cent
// metres de marche, c est une quinzaine de secondes : le combat est
// termine avant leur arrivee, et de l exterieur le mercenaire a
// simplement 芦 refuse d attaquer 禄.
//
// Ce seuil de 160 convient a un bot de terrain qui vagabonde ; pas a un
// membre de groupe, qui n a aucune raison de s eloigner autant de son
// employeur. On le ramene a deux fois la portee de recherche, soit 64
// metres -- largement au-dela des 32 metres ou l IA engage le combat et
// des 28 de portee des sorts, donc sans risque de teleporter un lanceur
// de sorts en pleine rotation.
else if (me->GetDistance(m_MasterPlayer->GetPosition()) > BOTAI_SEARCH_RANGE * 2)
{
m_Teleporting.SetTeleport(m_MasterPlayer, 0);
me->SetSelection(ObjectGuid::Empty);
@@ -3006,6 +3038,20 @@ Unit* BotGroupAI::GetBotAIValidSelectedUnit()
isValid = false;
if (!isValid)
{
// SONDE ATTACKDBG : nommer le filtre qui rejette, une fois par
// seconde au plus pour ne pas noyer le journal.
if (pTarget && m_AttackDbgTick + 1000 < getMSTime())
{
m_AttackDbgTick = getMSTime();
char const* motif = "inconnu";
if (!pTarget->IsVisible()) motif = "IsVisible";
else if (!me->InSamePhase(pTarget->GetPhaseShift())) motif = "phase";
else if (IsNotSelect(pTarget)) motif = "IsNotSelect";
else if (TargetIsControl(pTarget)) motif = "controle/evade";
else if (m_FliterCreatures.IsFliterCreature(pTarget->ToCreature())) motif = "filtre";
TC_LOG_ERROR("botai", "ATTACKDBG %s: cible %u rejetee -- %s.",
me->GetName().c_str(), pTarget->GetGUID().GetCounter(), motif);
}
me->AttackStop();
me->SetSelection(ObjectGuid::Empty);
return NULL;
+14 -8
View File
@@ -793,15 +793,21 @@ void MercenaryMgr::Update(uint32 diff)
// seulement de la creer, la ligne au-dessus. La tache partait avant
// sa cible et tombait dans le vide.
//
// On en repousse donc une maintenant que l IA existe. Elle attend
// que le bot soit hors combat avant d agir, le rehabillage complet
// faisant partie du meme passage.
// CORRECTION DU CORRECTIF (audit du 09/09) : la premiere version
// poussait un BGSType_DelayLevelup dans la file du bot. Mesure faite
// sur quatre mercenaires : deux repares, deux non. En cause,
// PushScheduleToQueue, qui jette SILENCIEUSEMENT une tache dans deux
// cas -- si une du meme type est deja en file, et, pour ce type
// precisement, si IsSettingFinish() est faux. La reparation ne
// partait donc qu au petit bonheur.
//
// On appelle directement. L IA de groupe vient d etre creee a la
// ligne precedente : le dynamic_cast<BotGroupAI*> qui ouvre
// OnLevelupToBotAI() aboutit forcement, et InitializeSpells re-resout
// les poignees contre le niveau, la specialisation et le grimoire
// definitifs. Rien ne peut plus l escamoter.
// =============================================================
if (PlayerBotSession* botSession = dynamic_cast<PlayerBotSession*>(bot->GetSession()))
{
BotGlobleSchedule reresolution(BotGlobleScheduleType::BGSType_DelayLevelup, bot->GetGUID());
botSession->PushScheduleToQueue(reresolution);
}
bot->OnLevelupToBotAI();
// Des maintenant, pas au tick suivant : l IA de groupe, en decouvrant
// un maitre lointain, armerait le sien vers l employeur, et SetTeleport
+24 -1
View File
@@ -405,7 +405,30 @@ bool PlayerBotSession::ProcessSetting(BotGlobleSchedule& schedule)
// heritage de l epoque ou LearnTalents() etait un corps vide.
PlayerTalentMap const* talents = player->GetTalentMap(player->GetActiveTalentGroup());
bool const hasTalents = talents && talents->size() >= player->CalculateTalentsTiers();
if (hasTalents && (schedule.parameter3 >= 4 || (player->FindTalentType() + 1 == schedule.parameter3)))
// SIGNALE EN JEU : « Kaerbrus n a meme pas d equipement ».
// Constate en base : niveau 110, 76 sorts, ZERO piece portee.
//
// Ce raccourci evite un re-level complet a un bot deja au bon
// niveau, avec ses talents et la bonne specialisation. Mais il
// saute du meme coup les etapes 6 a 10 de UpdateReset(), qui sont
// justement l habillage. Un mercenaire ayant perdu son equipement
// -- contrat precedent interrompu, re-level avorte -- ne le
// retrouvait donc JAMAIS : a chaque embauche, le raccourci
// concluait que tout allait bien et le renvoyait nu.
//
// On verifie donc qu il est reellement habille avant de couper.
// Dix pieces sur les dix-neuf emplacements : de quoi distinguer un
// bot equipe d un bot depouille, sans exiger un sans-faute (la
// seconde babiole et l arme de jet manquent souvent, legitimement).
uint32 piecesPortees = 0;
for (uint8 emplacement = EQUIPMENT_SLOT_START; emplacement < EQUIPMENT_SLOT_END; ++emplacement)
if (player->GetItemByPos(INVENTORY_SLOT_BAG_0, emplacement))
++piecesPortees;
bool const correctementEquipe = (piecesPortees >= 10);
if (hasTalents && correctementEquipe &&
(schedule.parameter3 >= 4 || (player->FindTalentType() + 1 == schedule.parameter3)))
return true;
}
}