From dffa54c44e1a3dd1af3e5e46d561e288fa505af9 Mon Sep 17 00:00:00 2001 From: devbox Date: Sun, 30 Aug 2026 13:49:45 +1000 Subject: [PATCH] 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). --- .../PlayerbotV2/Bot/States/State_InGroup.cpp | 9 +++++++ .../PlayerbotV2/CoreBridge/PlayerbotHooks.cpp | 1 + .../PlayerbotV2/CoreBridge/PlayerbotHooks.h | 1 + src/modules/PlayerbotV2/PlayerbotV2.cpp | 27 +++++++++++++++++++ src/modules/PlayerbotV2/PlayerbotV2.h | 1 + .../playerbotv2_core_hooks.thordekk.patch | 20 +++++++++++--- 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/modules/PlayerbotV2/Bot/States/State_InGroup.cpp b/src/modules/PlayerbotV2/Bot/States/State_InGroup.cpp index 152ccf3e8a..0f88fee804 100644 --- a/src/modules/PlayerbotV2/Bot/States/State_InGroup.cpp +++ b/src/modules/PlayerbotV2/Bot/States/State_InGroup.cpp @@ -14,6 +14,8 @@ #include "Services.h" #include "Travel/UnifiedTravelGraph.h" #include "SharedDefines.h" +#include "Player.h" +#include "ObjectAccessor.h" #include #include @@ -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 diff --git a/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.cpp b/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.cpp index 2f809ba9e1..4f73930edf 100644 --- a/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.cpp +++ b/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.cpp @@ -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); } diff --git a/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.h b/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.h index 2800e04bfa..8431a4c6ca 100644 --- a/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.h +++ b/src/modules/PlayerbotV2/CoreBridge/PlayerbotHooks.h @@ -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); diff --git a/src/modules/PlayerbotV2/PlayerbotV2.cpp b/src/modules/PlayerbotV2/PlayerbotV2.cpp index 4616df6347..e5942fcf6c 100644 --- a/src/modules/PlayerbotV2/PlayerbotV2.cpp +++ b/src/modules/PlayerbotV2/PlayerbotV2.cpp @@ -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(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; diff --git a/src/modules/PlayerbotV2/PlayerbotV2.h b/src/modules/PlayerbotV2/PlayerbotV2.h index 34c764db0c..f591ca4d7a 100644 --- a/src/modules/PlayerbotV2/PlayerbotV2.h +++ b/src/modules/PlayerbotV2/PlayerbotV2.h @@ -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); diff --git a/src/modules/PlayerbotV2/playerbotv2_core_hooks.thordekk.patch b/src/modules/PlayerbotV2/playerbotv2_core_hooks.thordekk.patch index ef3f45dd62..d74eaff818 100644 --- a/src/modules/PlayerbotV2/playerbotv2_core_hooks.thordekk.patch +++ b/src/modules/PlayerbotV2/playerbotv2_core_hooks.thordekk.patch @@ -1451,9 +1451,23 @@ index 1f376653f2..971fb43af8 100644 + Playerbot::Hooks::OnDeath(victim, attacker); +#endif + - // find player: owner of controlled `this` or `this` itself maybe - Player* player = nullptr; - if (attacker) + // 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 -- 2.47.3