From fff692e3f315a29f5eba62514794b15aece481f0 Mon Sep 17 00:00:00 2001 From: bash Date: Sun, 31 May 2026 15:29:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(Core/Movement):=20Mode-0=20transport=20boa?= =?UTF-8?q?rd/disembark=20=E2=80=94=20snap-and-deck-walk=20on=20board,=20N?= =?UTF-8?q?earTeleport+walk=20on=20disembark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Ai/Base/Actions/MovementActions.cpp | 160 +++++++++++++++++------- 1 file changed, 117 insertions(+), 43 deletions(-) diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 3f3f28f00..515c0ac86 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -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;