feat(Core/Movement): Mode-0 transport board/disembark — snap-and-deck-walk on board, NearTeleport+walk on disembark

This commit is contained in:
bash 2026-05-31 15:29:36 +02:00
parent 343ab7ac30
commit f90f365e94

View File

@ -3145,28 +3145,49 @@ bool MovementAction::WaitForTransport()
return false;
// Disembark: head is the transport node where we should get off,
// next is the world-position dock to land at. Reference uses
// UseTransport(ai, dock.entry, dock.point, tele.point, type>0)
// — we don't have UseTransport, so inline the equivalent.
// next is the world-position dock to land at. Reference UseTransport
// with doTeleport=(transportTeleportType > 0) routes to either
// MoveOffTransport-teleport (mode 1+) or MoveOffTransport-walk (mode 0).
PathNodePoint const& dock = path[0];
if (path.size() < 2)
return true; // no telePoint to land at; keep waiting
PathNodePoint const& tele = path[1];
(void)dock; // kept for reference parity (UseTransport signature)
transport->RemovePassenger(bot);
bot->StopMovingOnCurrentPos();
bool const teleported = bot->TeleportTo(tele.point.GetMapId(),
tele.point.GetPositionX(),
tele.point.GetPositionY(),
tele.point.GetPositionZ(),
bot->GetOrientation());
if (!teleported)
return true; // try again next tick
if (sPlayerbotAIConfig.transportTeleportType > 0)
{
// Mode 1+: teleport directly to exit.
bot->StopMovingOnCurrentPos();
bool const teleported = bot->TeleportTo(tele.point.GetMapId(),
tele.point.GetPositionX(),
tele.point.GetPositionY(),
tele.point.GetPositionZ(),
bot->GetOrientation());
if (!teleported)
return true; // try again next tick
lastMove.lastTransportEntry = 0;
return false;
}
// Mode 0: NearTeleportTo current world position (drops bot off
// transport plane to world coords without unloading client), then
// walk to the exit position. Matches reference MoveOffTransport
// with doTeleport=false.
bot->NearTeleportTo(bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), bot->GetOrientation());
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->Clear();
mm->MovePoint(0, tele.point.GetPositionX(),
tele.point.GetPositionY(),
tele.point.GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, true, false);
}
lastMove.lastTransportEntry = 0;
// Suppress unused-variable on `dock` — kept for parity with reference's
// UseTransport(entry, dockPoint, telePoint, ...) signature shape.
(void)dock;
return false;
}
@ -3253,9 +3274,10 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
case PathNodeType::NODE_TRANSPORT:
{
// Disembark: head is a transport node and bot is on one.
// Remove passenger + teleport to the next-step world position
// (cmangos uses UseTransport with current→next teleport here;
// our equivalent is RemovePassenger + TeleportTo).
// Reference dispatch: MoveOffTransport(exit, doTeleport) where
// doTeleport = (transportTeleportType > 0). Mode 1+ teleports
// straight to exit; mode 0 NearTeleports off transport plane
// then walks to exit.
if (!hasNext)
return false;
@ -3265,14 +3287,35 @@ bool MovementAction::HandleSpecialMovement(TravelPath& path)
PathNodePoint const& dst = path[1];
transport->RemovePassenger(bot);
bot->StopMovingOnCurrentPos();
bool const teleported = bot->TeleportTo(dst.point.GetMapId(),
dst.point.GetPositionX(),
dst.point.GetPositionY(),
dst.point.GetPositionZ(),
bot->GetOrientation());
if (sPlayerbotAIConfig.transportTeleportType > 0)
{
bot->StopMovingOnCurrentPos();
bool const teleported = bot->TeleportTo(dst.point.GetMapId(),
dst.point.GetPositionX(),
dst.point.GetPositionY(),
dst.point.GetPositionZ(),
bot->GetOrientation());
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0;
return teleported;
}
// Mode 0: NearTeleportTo current world pos (drops bot off
// transport plane without unloading the client), then walk
// to the exit.
bot->NearTeleportTo(bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), bot->GetOrientation());
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->Clear();
mm->MovePoint(0, dst.point.GetPositionX(),
dst.point.GetPositionY(),
dst.point.GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, true, false);
}
AI_VALUE(LastMovement&, "last movement").lastTransportEntry = 0;
return teleported;
return true;
}
default:
@ -3479,15 +3522,14 @@ bool MovementAction::BoardTransport(Transport* transport)
{
transport->AddPassenger(bot, true);
bot->StopMovingOnCurrentPos();
EmitDebugMove("Transport:board", "teleport", transport->GetPositionX(),
EmitDebugMove("Transport:board", "on-surface", transport->GetPositionX(),
transport->GetPositionY(), transport->GetPositionZ());
return true;
}
// Teleport-onto mode (transportTeleportType >= 1): skip the walk-toward
// phase and jump directly to a boarding edge. Matches reference's
// `UseTransport(...telep=true)` shortcut for invisible / random bots
// and any caller that doesn't want the visible boarding sequence.
// MoveOnTransport(doTeleport=true) — PlayerRelocation + AddPassenger.
if (sPlayerbotAIConfig.transportTeleportType >= 1)
{
float edgeX, edgeY, edgeZ;
@ -3506,33 +3548,65 @@ bool MovementAction::BoardTransport(Transport* transport)
return true;
}
}
// Fall through to walk-toward if no boarding point or teleport
// failed — better than refusing the board outright.
// Fall through to mode-0 if no boarding point — better than refusing.
}
// Walk-toward mode (transportTeleportType == 0, or mode>=1 fallback).
float destX = transport->GetPositionX();
float destY = transport->GetPositionY();
float destZ = transport->GetPositionZ();
// Mode 0: reference MoveOnTransport(doTeleport=false) — AddPassenger
// FIRST (snaps bot onto transport plane), then walk a path on deck.
// We don't have RandomPointOnTrans / transport-surface mmap, so when
// the bot is within INTERACTION_DISTANCE of the transport's boarding
// edge we approximate: AddPassenger + MovePoint toward the transport
// center as a "deck destination." Bot motion is then transport-
// relative because of the AddPassenger.
float boardX = transport->GetPositionX();
float boardY = transport->GetPositionY();
float boardZ = transport->GetPositionZ();
// Try to find nearest boarding edge
float edgeX, edgeY, edgeZ;
if (FindBoardingPointOnTransport(map, transport, transport, transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), edgeX, edgeY, edgeZ))
bool haveEdge = FindBoardingPointOnTransport(map, transport, transport,
transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(), bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), edgeX, edgeY, edgeZ);
if (haveEdge)
{
destX = edgeX;
destY = edgeY;
destZ = edgeZ;
boardX = edgeX;
boardY = edgeY;
boardZ = edgeZ;
}
// MovePoint without pathfinding (transport is a moving object)
float const dxBoard = bot->GetPositionX() - boardX;
float const dyBoard = bot->GetPositionY() - boardY;
bool const nearBoardingPoint =
(dxBoard * dxBoard + dyBoard * dyBoard) <
INTERACTION_DISTANCE * INTERACTION_DISTANCE;
if (nearBoardingPoint)
{
// Snap onto transport plane, then walk a short deck path so the
// bot is visibly on the boat (mirrors reference mode-0 deck walk).
transport->AddPassenger(bot, true);
if (MotionMaster* mm = bot->GetMotionMaster())
{
if (!bot->IsStandState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
mm->Clear();
mm->MovePoint(0, transport->GetPositionX(), transport->GetPositionY(),
transport->GetPositionZ(),
FORCED_MOVEMENT_RUN, 0.0f, 0.0f, false, false);
}
EmitDebugMove("Transport:board", "snap-and-deck-walk",
boardX, boardY, boardZ);
return true;
}
// Too far to snap — walk toward boarding edge.
if (MotionMaster* mm = bot->GetMotionMaster())
{
if (!bot->IsStandState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
mm->MovePoint(0, destX, destY, destZ, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, false, false);
EmitDebugMove("Transport:walk", "spline", destX, destY, destZ);
mm->MovePoint(0, boardX, boardY, boardZ,
FORCED_MOVEMENT_NONE, 0.0f, 0.0f, false, false);
EmitDebugMove("Transport:walk", "approach-boarding", boardX, boardY, boardZ);
}
return false;