Merge pull request 'Playerbot: bots hold position while owner rides a vehicle; re-summon to owner's location+phase on vehicle exit' (#1) from fix/bot-vehicle-follow into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-08-30 18:51:10 +10:00
6 changed files with 56 additions and 3 deletions
@@ -14,6 +14,8 @@
#include "Services.h"
#include "Travel/UnifiedTravelGraph.h"
#include "SharedDefines.h"
#include "Player.h"
#include "ObjectAccessor.h"
#include <cmath>
#include <limits>
@@ -843,6 +845,13 @@ void DispatchInGroup(BotAI& ai,
if (!in_active_bg && !dungeon_run_owns_movement && !detour_leased &&
dist > kRecallSlack && !snapshot.is_rooted())
{
// Vehicle rides (boar ride, cannons, ...): while the leader is on a
// vehicle, hold position instead of chasing the moving vehicle (bots
// would otherwise trail hopelessly behind an on-rails ride). The bots
// are re-summoned to the leader once the ride ends (vehicle dismount).
if (Player* anchor_player = ObjectAccessor::FindConnectedPlayer(anchor->guid))
if (anchor_player->GetVehicle())
return;
// Follow uses MotionMaster MoveFollow, which silently re-emits forever
// when the leader is unreachable (across a gap / closed door) — and
// grouped bots are exempt from GlobalStuckRescue, so they have no other
@@ -15,6 +15,7 @@ void OnPlayerLogout(Player* p) { V2::Module::instance().OnPlayerLogout(p); }
void OnLevelUp(Player* p, uint8 new_level) { V2::Module::instance().OnLevelUp(p, new_level); }
void OnPlayerMounted(Player* p) { V2::Module::instance().OnMounted(p); }
void OnPlayerDismounted(Player* p) { V2::Module::instance().OnDismounted(p); }
void OnPlayerVehicleExit(Player* p) { V2::Module::instance().OnVehicleExit(p); }
void OnDeath(Unit* victim, Unit* killer) { V2::Module::instance().OnDeath(victim, killer); }
void OnResurrect(Player* p) { V2::Module::instance().OnResurrect(p); }
void OnSpecChanged(Player* p, uint8 new_spec) { V2::Module::instance().OnSpecChanged(p, new_spec); }
@@ -28,6 +28,7 @@ void OnPlayerLogout(Player* p);
void OnLevelUp(Player* p, uint8 new_level);
void OnPlayerMounted(Player* p);
void OnPlayerDismounted(Player* p);
void OnPlayerVehicleExit(Player* p);
void OnDeath(Unit* victim, Unit* killer);
void OnResurrect(Player* p);
void OnSpecChanged(Player* p, uint8 new_spec);
+27
View File
@@ -2944,6 +2944,33 @@ void Module::OnDismounted(Player* owner)
}
}
void Module::OnVehicleExit(Player* owner)
{
// After an on-rails vehicle ride (Exile's Reach boar ride, ...) the bots
// held position instead of following the moving vehicle. Now that the
// player is off, summon them back to the player's location and phase so
// they actually appear (bots were spawning at coords but not in the
// player's phase shift).
if (!initialized_ || !owner) return;
WorldSession* sess = owner->GetSession();
if (!sess || sess->IsBot()) return;
uint32 const account_id = sess->GetAccountId();
if (account_id == 0) return;
auto bots = Services::Altbots().AltsOfAccount(account_id);
for (BotId id : bots)
{
Player* bot = ObjectAccessor::FindConnectedPlayer(
ObjectGuid::Create<HighGuid::Player>(id));
if (!bot || !bot->IsInWorld()) continue;
if (bot->IsInCombat() || !bot->IsAlive()) continue;
PhasingHandler::InheritPhaseShift(bot, owner);
BotMovement::SafeNearTeleport(bot, owner->GetPositionX(),
owner->GetPositionY(), owner->GetPositionZ(), owner->GetOrientation());
}
}
void Module::OnDeath(Unit* victim, Unit* /*killer*/)
{
if (!initialized_ || !victim) return;
+1
View File
@@ -50,6 +50,7 @@ public:
void OnLevelUp(Player* p, uint8 new_level);
void OnMounted(Player* owner);
void OnDismounted(Player* owner);
void OnVehicleExit(Player* owner);
void OnDeath(Unit* victim, Unit* killer);
void OnResurrect(Player* p);
void OnSpecChanged(Player* p, uint8 new_spec);
@@ -1454,6 +1454,20 @@ index 1f376653f2..971fb43af8 100644
// find player: owner of controlled `this` or `this` itself maybe
Player* player = nullptr;
if (attacker)
@@ -13071,5 +13113,13 @@ void Unit::_ExitVehicle(Position const* exitPosition)
// If the player is on mounted duel and exits the mount, he should immediatly lose the duel
if (player && player->duel && player->duel->IsMounted)
player->DuelComplete(DUEL_FLED);
+#if TRINITY_PLAYERBOT_V2
+ // PlayerbotV2: a human owner leaving a vehicle (Exile's Reach boar ride,
+ // ...) tells owned bots to re-summon to his location + phase (they held
+ // position while the vehicle moved).
+ if (player && player->GetSession() && !player->GetSession()->IsBot())
+ Playerbot::Hooks::OnPlayerVehicleExit(player);
+#endif
+
SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT
diff --git a/src/server/game/Groups/GroupMgr.h b/src/server/game/Groups/GroupMgr.h
index 58951339b2..7ce860e770 100644
--- a/src/server/game/Groups/GroupMgr.h