Playerbot: bots hold position while owner rides a vehicle; re-summon to owner's location+phase on vehicle exit

- State_InGroup follow-recall: skip following when the anchor is on a vehicle
  (Exile's Reach boar ride) so bots don't trail an on-rails ride.
- New OnPlayerVehicleExit hook (wired in the core-hooks patch at
  Unit::_ExitVehicle): after the owner leaves a vehicle, teleport owned bots
  to the owner's position and inherit the owner's PhaseShift so they actually
  appear (bots were spawning at coords but not in the player's phase).
This commit is contained in:
devbox
2026-08-30 13:49:45 +10:00
parent f1be9a147d
commit dffa54c44e
6 changed files with 56 additions and 3 deletions
@@ -14,6 +14,8 @@
#include "Services.h" #include "Services.h"
#include "Travel/UnifiedTravelGraph.h" #include "Travel/UnifiedTravelGraph.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "Player.h"
#include "ObjectAccessor.h"
#include <cmath> #include <cmath>
#include <limits> #include <limits>
@@ -843,6 +845,13 @@ void DispatchInGroup(BotAI& ai,
if (!in_active_bg && !dungeon_run_owns_movement && !detour_leased && if (!in_active_bg && !dungeon_run_owns_movement && !detour_leased &&
dist > kRecallSlack && !snapshot.is_rooted()) 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 // Follow uses MotionMaster MoveFollow, which silently re-emits forever
// when the leader is unreachable (across a gap / closed door) — and // when the leader is unreachable (across a gap / closed door) — and
// grouped bots are exempt from GlobalStuckRescue, so they have no other // 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 OnLevelUp(Player* p, uint8 new_level) { V2::Module::instance().OnLevelUp(p, new_level); }
void OnPlayerMounted(Player* p) { V2::Module::instance().OnMounted(p); } void OnPlayerMounted(Player* p) { V2::Module::instance().OnMounted(p); }
void OnPlayerDismounted(Player* p) { V2::Module::instance().OnDismounted(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 OnDeath(Unit* victim, Unit* killer) { V2::Module::instance().OnDeath(victim, killer); }
void OnResurrect(Player* p) { V2::Module::instance().OnResurrect(p); } void OnResurrect(Player* p) { V2::Module::instance().OnResurrect(p); }
void OnSpecChanged(Player* p, uint8 new_spec) { V2::Module::instance().OnSpecChanged(p, new_spec); } 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 OnLevelUp(Player* p, uint8 new_level);
void OnPlayerMounted(Player* p); void OnPlayerMounted(Player* p);
void OnPlayerDismounted(Player* p); void OnPlayerDismounted(Player* p);
void OnPlayerVehicleExit(Player* p);
void OnDeath(Unit* victim, Unit* killer); void OnDeath(Unit* victim, Unit* killer);
void OnResurrect(Player* p); void OnResurrect(Player* p);
void OnSpecChanged(Player* p, uint8 new_spec); 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*/) void Module::OnDeath(Unit* victim, Unit* /*killer*/)
{ {
if (!initialized_ || !victim) return; if (!initialized_ || !victim) return;
+1
View File
@@ -50,6 +50,7 @@ public:
void OnLevelUp(Player* p, uint8 new_level); void OnLevelUp(Player* p, uint8 new_level);
void OnMounted(Player* owner); void OnMounted(Player* owner);
void OnDismounted(Player* owner); void OnDismounted(Player* owner);
void OnVehicleExit(Player* owner);
void OnDeath(Unit* victim, Unit* killer); void OnDeath(Unit* victim, Unit* killer);
void OnResurrect(Player* p); void OnResurrect(Player* p);
void OnSpecChanged(Player* p, uint8 new_spec); void OnSpecChanged(Player* p, uint8 new_spec);
@@ -1451,9 +1451,23 @@ index 1f376653f2..971fb43af8 100644
+ Playerbot::Hooks::OnDeath(victim, attacker); + Playerbot::Hooks::OnDeath(victim, attacker);
+#endif +#endif
+ +
// find player: owner of controlled `this` or `this` itself maybe // find player: owner of controlled `this` or `this` itself maybe
Player* player = nullptr; Player* player = nullptr;
if (attacker) 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 diff --git a/src/server/game/Groups/GroupMgr.h b/src/server/game/Groups/GroupMgr.h
index 58951339b2..7ce860e770 100644 index 58951339b2..7ce860e770 100644
--- a/src/server/game/Groups/GroupMgr.h --- a/src/server/game/Groups/GroupMgr.h