PlayerBot : ordres stop et summon operants, rotation des mercenaires

Trois defauts distincts, tous verifies en jeu par sondes temporaires puis par
comparaison des positions en base.

1. L ordre « stop » n arretait rien. Le suivi est un generateur MoveFollow pose
   sur le MotionMaster ; ClearMovement() se contente d appeler StopMoving() et
   le generateur, toujours en tete de pile, relançait la poursuite au tick
   suivant. ProcessStopCommand vide desormais la pile de mouvement.

2. Le teleport d un bot aboutissait cote serveur - positions identiques en base
   apres un « summon » - mais les clients continuaient de l afficher a son
   ancienne place : l etape finale diffuse un paquet portant un opcode CLIENT
   (CMSG_MOVE_FALL_LAND) que les clients voisins ne savent pas lire. L objet est
   maintenant reconstruit chez les joueurs alentour, et le MotionMaster vide a
   chaque acquittement, comme le fait mod-playerbots dans HandleTeleportAck.

3. Cote module, l invocation posait un TeleportTo direct : meme cause, meme
   effet, le mercenaire semblait n arriver jamais au portail. Elle passe
   desormais par l ordre « summon » de l IA de groupe, au tick qui suit l entree
   dans le groupe - le temps que l IA reconnaisse son maitre.

Enfin, FindCandidate retournait le premier compte bot eligible d une carte
ordonnee par identifiant : c etait invariablement le meme mercenaire. Tous les
candidats sont desormais collectes puis tires au sort, en ligne comme hors
ligne, un seul personnage par compte pour ne pas biaiser le tirage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
SylvaniaCore deploy
2026-08-16 15:30:31 +02:00
parent 70e47be8b6
commit 5aeeb91c52
4 changed files with 72 additions and 13 deletions
+14
View File
@@ -1043,6 +1043,20 @@ void BotAITeleport::Update(uint32 diff, BotBGAIMovement* pMovement)
me->SendMessageToSet(&data, me);
me->CombatStop(true);
me->SetSelection(ObjectGuid::Empty);
// SylvaniaCore : le paquet diffuse ci-dessus porte un opcode CLIENT
// (CMSG_MOVE_FALL_LAND), que les clients voisins ne savent pas lire.
// Resultat : le bot avait bien change de place cote serveur - positions
// identiques en base - mais restait affiche a son ancien emplacement,
// jusqu au prochain passage hors puis dans le champ de vision. On force
// donc la reconstruction de l objet chez les joueurs alentour.
me->DestroyForNearbyPlayers();
me->UpdateObjectVisibility(true);
// mod-playerbots (PlayerbotAI::HandleTeleportAck) vide le MotionMaster
// et arrete le mouvement a chaque acquittement de teleport. Sans cela,
// le generateur de suivi encore en place fait repartir le bot depuis
// l ancienne trajectoire.
me->GetMotionMaster()->Clear(true);
me->StopMoving();
m_TeleportPositon.m_positionX = m_TeleportPositon.m_positionY = m_TeleportPositon.m_positionZ = 0;
m_TeleportStep = 0;
}
@@ -285,6 +285,14 @@ void BotGroupAI::ProcessStopCommand()
me->SetSelection(ObjectGuid::Empty);
m_SeduceTarget = ObjectGuid::Empty;
m_StopFollow = true;
// SylvaniaCore : sans ces trois lignes, l ordre restait sans effet visible.
// Le suivi est un generateur MoveFollow pose sur le MotionMaster ;
// ClearMovement() se contente d appeler StopMoving(), et le generateur,
// toujours en tete de pile, relance la poursuite au tick suivant. Il faut
// vider la pile de mouvement pour que le mercenaire se fige vraiment.
m_Movement->ClearMovement();
me->GetMotionMaster()->Clear();
me->StopMoving();
}
void BotGroupAI::ProcessSetting()
+44 -12
View File
@@ -16,6 +16,7 @@
*/
#include "MercenaryMgr.h"
#include "BotGroupAI.h"
#include "CapitalSiegeMgr.h"
#include "Chat.h"
#include "Config.h"
@@ -161,6 +162,8 @@ bool MercenaryMgr::IsAccountHired(uint32 accountId) const
bool MercenaryMgr::FindCandidate(Player* owner, uint8 role, uint32& accountId, uint64& charGuid, bool& alreadyOnline) const
{
SessionMap const& sessions = sWorld->GetAllSessions();
std::vector<uint32> onlineCandidates;
std::vector<std::pair<uint32, uint64> > offlineCandidates;
for (SessionMap::const_iterator it = sessions.begin(); it != sessions.end(); ++it)
{
@@ -194,7 +197,15 @@ bool MercenaryMgr::FindCandidate(Player* owner, uint8 role, uint32& accountId, u
if (FindSpecIndexForRole(bot->getClass(), role) < 0)
continue;
accountId = session->GetAccountId();
onlineCandidates.push_back(session->GetAccountId());
}
// Tirage au sort parmi TOUS les candidats, et non premier trouve : la carte
// des sessions est ordonnee par identifiant de compte, si bien qu un choix
// sequentiel ramenait invariablement le meme mercenaire.
if (!onlineCandidates.empty())
{
accountId = onlineCandidates[urand(0, uint32(onlineCandidates.size()) - 1)];
charGuid = 0;
alreadyOnline = true;
return true;
@@ -235,13 +246,21 @@ bool MercenaryMgr::FindCandidate(Player* owner, uint8 role, uint32& accountId, u
if (FindSpecIndexForRole(uint8(charInfo.profession), role) < 0)
continue;
accountId = session->GetAccountId();
charGuid = charInfo.guid;
alreadyOnline = false;
return true;
offlineCandidates.push_back(std::make_pair(session->GetAccountId(), charInfo.guid));
break; // un seul personnage retenu par compte
}
}
if (!offlineCandidates.empty())
{
std::pair<uint32, uint64> const& picked =
offlineCandidates[urand(0, uint32(offlineCandidates.size()) - 1)];
accountId = picked.first;
charGuid = picked.second;
alreadyOnline = false;
return true;
}
return false;
}
@@ -518,6 +537,22 @@ void MercenaryMgr::Update(uint32 diff)
ReleaseBot(contract);
continue;
}
// Materialisation aupres de l employeur, au premier tick qui suit
// l entree dans le groupe. On passe par l ordre « summon » de l IA
// plutot que par un TeleportTo direct : un bot n a pas de client
// pour accuser reception du teleport, c est BotAITeleport qui
// simule cet echange en trois etapes. Un TeleportTo pose ici laisse
// le mercenaire a son ancienne place aux yeux de tout le monde.
if (it->summonPending)
{
if (BotGroupAI* groupAI = dynamic_cast<BotGroupAI*>(bot->GetAI()))
{
groupAI->ProcessBotCommand(owner, "summon");
it->summonPending = false;
}
}
++it;
continue;
}
@@ -581,16 +616,13 @@ void MercenaryMgr::Update(uint32 diff)
it->botGuid = bot->GetGUID();
it->stage = MERC_STAGE_ACTIVE;
// Le mercenaire doit encore se materialiser aupres de son employeur :
// c est fait au tick suivant, une fois que l IA de groupe aura reconnu
// son maitre. Voir la branche MERC_STAGE_ACTIVE.
it->summonPending = true;
PlayerBotMgr::SwitchPlayerBotAI(bot, PlayerBotAIType::PBAIT_GROUP, true);
// Le mercenaire se materialise aupres de son employeur. L ajout au
// groupe precede volontairement le teleport : c est ce qui autorise
// l entree dans l instance ou se trouve deja le joueur.
float x, y, z;
owner->GetClosePoint(x, y, z, 2.0f, 3.0f);
bot->TeleportTo(owner->GetMapId(), x, y, z, owner->GetOrientation());
ChatHandler(owner->GetSession()).PSendSysMessage(
"|cff00ff00[Portail]|r %s, %s mercenaire, répond à votre appel.",
bot->GetName().c_str(), GetRoleName(it->role));
+6 -1
View File
@@ -64,7 +64,7 @@ enum MercenaryResult
struct MercenaryContract
{
MercenaryContract() : accountId(0), role(0), stage(MERC_STAGE_SUMMONING), waitSeconds(0),
pendingRelease(false) { }
pendingRelease(false), summonPending(false) { }
uint32 accountId; // compte bot reserve
ObjectGuid ownerGuid; // joueur qui a paye
@@ -76,6 +76,11 @@ struct MercenaryContract
// Contrat rompu par un hook de groupe du core, mais dont la liberation est
// differee au prochain Update() : voir MercenaryMgr::OnPlayerLeftGroup().
bool pendingRelease;
// Le mercenaire est entre dans le groupe mais n a pas encore ete rappele
// aupres de son employeur : l ordre part au tick suivant, quand l IA de
// groupe aura reconnu son maitre.
bool summonPending;
};
class TC_GAME_API MercenaryMgr