From cbd5f8748cfe000f2e0e627d5616e218e0abe5e6 Mon Sep 17 00:00:00 2001 From: bash Date: Sun, 31 May 2026 18:53:11 +0200 Subject: [PATCH] fix(Core/Movement): Align HandleSpecialMovement + ClipPath details with reference --- src/Ai/Base/Actions/MovementActions.cpp | 44 ++++++++++++++++++++++--- src/Mgr/Travel/TravelNode.cpp | 17 +++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 47ca2b48f..45d8ee69a 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -2625,17 +2625,43 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path) uint32 const spellId = goInfo->spellcaster.spellId; SpellInfo const* spellInfo = SpellMgr::instance()->GetSpellInfo(spellId); - if (!spellInfo || !spellInfo->HasEffect(SPELL_EFFECT_TELEPORT_UNITS)) + if (!spellInfo) return false; - // Mounted handling: refuse the interact while flying high - // (the dismount would drop the bot). Otherwise dismount. + // EffectTriggerSpell indirection: some portal GOs cast a + // spell that triggers ANOTHER spell which is the actual + // teleport. Follow the chain once before the TELEPORT_UNITS + // check, matching reference behaviour. + if (spellInfo->Effects[0].TriggerSpell) + { + if (SpellInfo const* triggered = + sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell)) + spellInfo = triggered; + } + + if (!spellInfo->HasEffect(SPELL_EFFECT_TELEPORT_UNITS)) + return false; + + // Mounted handling: dismount if not flying, or if flying low + // enough that the drop is safe (reference threshold: 10y + // above ground). Refuse the interact otherwise. if (bot->IsMounted()) { if (bot->IsFlying()) - return false; + { + float const groundZ = bot->GetMap()->GetHeight( + bot->GetPhaseMask(), + bot->GetPositionX(), + bot->GetPositionY(), + bot->GetPositionZ()); + if (bot->GetPositionZ() - groundZ > 10.0f) + return false; + } bot->Dismount(); } + // AC-side defensive addition (no reference parallel): some + // shapeshift forms block GO interact. Harmless when not in + // form. botAI->RemoveShapeshift(); GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects"); @@ -2644,7 +2670,11 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path) GameObject* go = botAI->GetGameObject(guid); if (!go || go->GetEntry() != cur.entry) continue; - if (!bot->GetGameObjectIfCanInteractWith(guid, GAMEOBJECT_TYPE_SPELLCASTER)) + // MAX_GAMEOBJECT_TYPE accepts any type — reference uses + // this rather than restricting to SPELLCASTER, so GOOBER + // portals (which we accept at the goInfo->type check + // above) aren't filtered out here. + if (!bot->GetGameObjectIfCanInteractWith(guid, MAX_GAMEOBJECT_TYPE)) continue; WorldPacket packet(CMSG_GAMEOBJ_USE); @@ -2700,6 +2730,10 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path) dst.point.GetPositionZ(), bot->GetOrientation()); AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0; + // Throttle re-evaluation after disembark — reference adds a + // 1s WaitForReach here to prevent rapid re-tries while the + // post-teleport state settles. + WaitForReach(1000.0f); return teleported; } diff --git a/src/Mgr/Travel/TravelNode.cpp b/src/Mgr/Travel/TravelNode.cpp index 7cec2b604..532e3e9fd 100644 --- a/src/Mgr/Travel/TravelNode.cpp +++ b/src/Mgr/Travel/TravelNode.cpp @@ -12,6 +12,7 @@ #include #include "BudgetValues.h" +#include "DBCStores.h" #include "MapMgr.h" #include "PathGenerator.h" #include "Playerbots.h" @@ -757,6 +758,14 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos, { if (startP->entry) { + // Reference also verifies the DBC AreaTriggerEntry record + // exists (cmangos sAreaTriggerStore.LookupEntry). AC's + // sAreaTriggerStore is the same DBC store. Skip the trigger + // node if the DBC record is missing — likely a stale entry + // in the baked dataset. + if (!sAreaTriggerStore.LookupEntry(startP->entry)) + return false; + AreaTrigger const* at = sObjectMgr->GetAreaTrigger(startP->entry); if (!at) return false; @@ -876,7 +885,13 @@ void TravelPath::ClipPath(PlayerbotAI* ai, Unit* mover, bool ignoreEnemyTargets) float const range = cre->GetAttackDistance(mover); if (WorldPosition(unit).sqDistance(p->point) > range * range) continue; - if (!unit->IsHostileTo(mover) || !unit->IsWithinLOSInMap(mover)) + // Reference uses CanAttackOnSight (faction + sanctuary + + // feign + phased + passive flags). AC equivalent is + // CanCreatureAttack with skipDistCheck=true (range already + // confirmed above). IsHostileTo alone over-clipped at + // neutral mobs that wouldn't actually aggro. + if (!cre->CanCreatureAttack(mover, true) || + !unit->IsWithinLOSInMap(mover)) continue; endP = p;