fix(Core/Movement): HandleSpecialMovement parity — portal spell-effect check, mount-flying refuse, area-trigger orientation, transport board throttle, teleport failure clears lastPath

This commit is contained in:
bash 2026-05-31 00:26:30 +02:00
parent 6b6f61a89d
commit 80a0e79cd2

View File

@ -3161,8 +3161,26 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
if (!cur.entry) if (!cur.entry)
return false; return false;
// Validate the GO template is actually a teleport spellcaster.
// Rejects mis-labeled portal entries before we waste a CMSG.
GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(cur.entry);
if (!goInfo || (goInfo->type != GAMEOBJECT_TYPE_SPELLCASTER &&
goInfo->type != GAMEOBJECT_TYPE_GOOBER))
return false;
uint32 const spellId = goInfo->spellcaster.spellId;
SpellInfo const* spellInfo = SpellMgr::instance()->GetSpellInfo(spellId);
if (!spellInfo || !spellInfo->HasEffect(SPELL_EFFECT_TELEPORT_UNITS))
return false;
// Mounted handling: refuse the interact while flying high
// (the dismount would drop the bot). Otherwise dismount.
if (bot->IsMounted()) if (bot->IsMounted())
{
if (bot->IsFlying())
return false;
bot->Dismount(); bot->Dismount();
}
botAI->RemoveShapeshift(); botAI->RemoveShapeshift();
GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects"); GuidVector nearGOs = AI_VALUE(GuidVector, "nearest game objects");
@ -3193,6 +3211,8 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
return false; return false;
} }
// No entry: direct teleport to next-point destination. // No entry: direct teleport to next-point destination.
// Reference uses the next point's stored orientation (the
// baked exit facing), not the bot's current facing.
if (hasNext) if (hasNext)
{ {
PathNodePoint const& dst = path[1]; PathNodePoint const& dst = path[1];
@ -3200,7 +3220,7 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
dst.point.GetPositionX(), dst.point.GetPositionX(),
dst.point.GetPositionY(), dst.point.GetPositionY(),
dst.point.GetPositionZ(), dst.point.GetPositionZ(),
bot->GetOrientation()); dst.point.GetOrientation());
} }
return false; return false;
} }
@ -3248,19 +3268,23 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
Map* map = bot->GetMap(); Map* map = bot->GetMap();
if (!map) if (!map)
return false; return false;
// Always consume the tick (return true) + throttle 1s,
// matching reference. Prevents per-tick board retries
// while we wait for the transport to actually receive us.
Transport* transport = GetTransportForPosTolerant( Transport* transport = GetTransportForPosTolerant(
map, bot, bot->GetPhaseMask(), map, bot, bot->GetPhaseMask(),
next.point.GetPositionX(), next.point.GetPositionX(),
next.point.GetPositionY(), next.point.GetPositionY(),
next.point.GetPositionZ()); next.point.GetPositionZ());
if (!transport || transport->GetEntry() != next.entry) if (transport && transport->GetEntry() == next.entry)
return false;
if (BoardTransport(transport))
{ {
if (BoardTransport(transport))
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = next.entry; AI_VALUE(LastMovement&, "last movement").lastTransportEntry = next.entry;
return true;
} }
return false;
WaitForReach(1000.0f);
return true;
} }
case PathNodeType::NODE_FLIGHTPATH: case PathNodeType::NODE_FLIGHTPATH:
@ -3309,26 +3333,36 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
// Can't cast while flying — let the bot land first. // Can't cast while flying — let the bot land first.
bool const canCastNow = !bot->IsFlying(); bool const canCastNow = !bot->IsFlying();
// Hearthstone (item 8690) — fire if available and not flying high. if (next.entry == 8690) // Hearthstone
if (next.entry == 8690)
{ {
if (canCastNow) if (canCastNow)
return botAI->DoSpecificAction("hearthstone", {
bool const ok = botAI->DoSpecificAction("hearthstone",
Event("move action"), true); Event("move action"), true);
return false; if (ok)
return true;
} }
}
// Other teleport spells: dismount, drop shapeshift, queue cast. else if (canCastNow)
// Skipped while flying high — caller's next-tick walk handles {
// the descent. // Mage city portal / similar spell — dismount, drop
if (!canCastNow) // shapeshift, queue cast. We don't gate on reagents (no
return false; // "has reagents for" value on AC); the server-side cast
// attempt will fail cleanly if reagents are missing.
if (bot->IsMounted()) if (bot->IsMounted())
bot->Dismount(); bot->Dismount();
botAI->RemoveShapeshift(); botAI->RemoveShapeshift();
return botAI->DoSpecificAction( if (botAI->DoSpecificAction(
"cast", "cast",
Event("rpg action", std::to_string(next.entry)), true); Event("rpg action", std::to_string(next.entry)), true))
return true;
}
// Cast didn't happen or failed — clear the cached path so
// the next tick re-resolves cleanly instead of retrying the
// same teleport edge that just failed.
AI_VALUE(LastMovement&, "last movement").setPath(TravelPath());
return false;
} }
default: default: