fix(Core/Movement): Align HandleSpecialMovement + ClipPath details with reference

This commit is contained in:
bash 2026-05-31 18:53:11 +02:00
parent 1d85510a9e
commit cbd5f8748c
2 changed files with 55 additions and 6 deletions

View File

@ -2625,17 +2625,43 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
uint32 const spellId = goInfo->spellcaster.spellId; uint32 const spellId = goInfo->spellcaster.spellId;
SpellInfo const* spellInfo = SpellMgr::instance()->GetSpellInfo(spellId); SpellInfo const* spellInfo = SpellMgr::instance()->GetSpellInfo(spellId);
if (!spellInfo || !spellInfo->HasEffect(SPELL_EFFECT_TELEPORT_UNITS)) if (!spellInfo)
return false; return false;
// Mounted handling: refuse the interact while flying high // EffectTriggerSpell indirection: some portal GOs cast a
// (the dismount would drop the bot). Otherwise dismount. // 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->IsMounted())
{ {
if (bot->IsFlying()) 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(); bot->Dismount();
} }
// AC-side defensive addition (no reference parallel): some
// shapeshift forms block GO interact. Harmless when not in
// form.
botAI->RemoveShapeshift(); botAI->RemoveShapeshift();
GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects"); GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects");
@ -2644,7 +2670,11 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
GameObject* go = botAI->GetGameObject(guid); GameObject* go = botAI->GetGameObject(guid);
if (!go || go->GetEntry() != cur.entry) if (!go || go->GetEntry() != cur.entry)
continue; 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; continue;
WorldPacket packet(CMSG_GAMEOBJ_USE); WorldPacket packet(CMSG_GAMEOBJ_USE);
@ -2700,6 +2730,10 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
dst.point.GetPositionZ(), dst.point.GetPositionZ(),
bot->GetOrientation()); bot->GetOrientation());
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0; 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; return teleported;
} }

View File

@ -12,6 +12,7 @@
#include <unordered_set> #include <unordered_set>
#include "BudgetValues.h" #include "BudgetValues.h"
#include "DBCStores.h"
#include "MapMgr.h" #include "MapMgr.h"
#include "PathGenerator.h" #include "PathGenerator.h"
#include "Playerbots.h" #include "Playerbots.h"
@ -757,6 +758,14 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
{ {
if (startP->entry) 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); AreaTrigger const* at = sObjectMgr->GetAreaTrigger(startP->entry);
if (!at) if (!at)
return false; return false;
@ -876,7 +885,13 @@ void TravelPath::ClipPath(PlayerbotAI* ai, Unit* mover, bool ignoreEnemyTargets)
float const range = cre->GetAttackDistance(mover); float const range = cre->GetAttackDistance(mover);
if (WorldPosition(unit).sqDistance(p->point) > range * range) if (WorldPosition(unit).sqDistance(p->point) > range * range)
continue; 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; continue;
endP = p; endP = p;