From 65d5978f4a383eac19997aed963370751c6b184a Mon Sep 17 00:00:00 2001 From: BlaMacfly <165155133+BlaMacfly@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:25:34 +0200 Subject: [PATCH] Mercenaires : trois correctifs issus de l audit du module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../AI/PlayerAI/BotGroupAI/BotGroupAI.cpp | 48 ++++++++++++++++++- src/server/game/Mercenary/MercenaryMgr.cpp | 22 +++++---- src/server/game/Server/PlayerBotSession.cpp | 25 +++++++++- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/server/game/AI/PlayerAI/BotGroupAI/BotGroupAI.cpp b/src/server/game/AI/PlayerAI/BotGroupAI/BotGroupAI.cpp index 6315f53..4d68f3f 100644 --- a/src/server/game/AI/PlayerAI/BotGroupAI/BotGroupAI.cpp +++ b/src/server/game/AI/PlayerAI/BotGroupAI/BotGroupAI.cpp @@ -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; diff --git a/src/server/game/Mercenary/MercenaryMgr.cpp b/src/server/game/Mercenary/MercenaryMgr.cpp index 7d3044d..6f00e15 100644 --- a/src/server/game/Mercenary/MercenaryMgr.cpp +++ b/src/server/game/Mercenary/MercenaryMgr.cpp @@ -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 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(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 diff --git a/src/server/game/Server/PlayerBotSession.cpp b/src/server/game/Server/PlayerBotSession.cpp index 581cbd9..a7576a7 100644 --- a/src/server/game/Server/PlayerBotSession.cpp +++ b/src/server/game/Server/PlayerBotSession.cpp @@ -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; } }