feat(net+app): Phase B.3 — portal-space state machine for teleports
PlayerTeleport (0xF751) is a standalone GameMessage (u16 sequence, align-4). When received, WorldSession fires TeleportStarted(uint sequence). GameWindow subscribes: OnTeleportStarted sets PlayerMovementController.State = PortalSpace, freezing all WASD/physics input. OnLivePositionUpdated detects arrival (different landblock or >100 unit jump on our character guid), recenters the streaming origin, resolves physics for ground Z, snaps the player entity + controller, returns State to InWorld, and sends GameActionLoginComplete directly (matching holtburger's PlayerTeleport handler: send_login_complete on every portal transition). PlayerMovementController gains PlayerState enum + early-return guard: if State == PortalSpace, Update() returns a zero-movement result immediately so no MoveToState / AutonomousPosition messages are emitted during transit. WorldSession gains ResetLoginComplete() for callers that need to re-arm the latch (documented; not called by the teleport path since we send LoginComplete directly rather than through the PlayerCreate latch). Opcode source: holtburger/crates/holtburger-protocol/src/opcodes.rs:84 Wire layout: holtburger/crates/.../movement/messages/teleport.rs Build: 0 errors. Tests: 283 passed, 0 failed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
777893783a
commit
ae06f9c0ff
3 changed files with 153 additions and 0 deletions
|
|
@ -33,6 +33,14 @@ public readonly record struct MovementResult(
|
|||
float? SidestepSpeed,
|
||||
float? TurnSpeed);
|
||||
|
||||
/// <summary>
|
||||
/// Portal-space state for the player movement controller.
|
||||
/// PortalSpace freezes all movement input while the server is moving the
|
||||
/// player through a portal — resumed once the destination UpdatePosition
|
||||
/// arrives and the player is snapped to the new location.
|
||||
/// </summary>
|
||||
public enum PlayerState { InWorld, PortalSpace }
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame player movement controller. Reads input, drives the
|
||||
/// physics engine, tracks motion state for animation + server messages.
|
||||
|
|
@ -60,6 +68,15 @@ public sealed class PlayerMovementController
|
|||
public float GravityAccel { get; set; } = 20f;
|
||||
public float AirControlFactor { get; set; } = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Current portal-space state. Set to PortalSpace when the server sends
|
||||
/// PlayerTeleport (0xF751); set back to InWorld once the destination
|
||||
/// UpdatePosition arrives and the player is snapped to the new cell.
|
||||
/// While in PortalSpace, Update returns immediately with a zero-movement
|
||||
/// result so no WASD input or physics is processed.
|
||||
/// </summary>
|
||||
public PlayerState State { get; set; } = PlayerState.InWorld;
|
||||
|
||||
public float Yaw { get; set; }
|
||||
public Vector3 Position { get; private set; }
|
||||
public uint CellId { get; private set; }
|
||||
|
|
@ -87,6 +104,24 @@ public sealed class PlayerMovementController
|
|||
|
||||
public MovementResult Update(float dt, MovementInput input)
|
||||
{
|
||||
// Portal-space guard: while teleporting, no input is processed and
|
||||
// no physics is resolved. Return a zero-movement result so the caller
|
||||
// can detect the frozen state (MotionStateChanged = false, no commands).
|
||||
if (State == PlayerState.PortalSpace)
|
||||
{
|
||||
return new MovementResult(
|
||||
Position: Position,
|
||||
CellId: CellId,
|
||||
IsOnGround: !IsAirborne,
|
||||
MotionStateChanged: false,
|
||||
ForwardCommand: null,
|
||||
SidestepCommand: null,
|
||||
TurnCommand: null,
|
||||
ForwardSpeed: null,
|
||||
SidestepSpeed: null,
|
||||
TurnSpeed: null);
|
||||
}
|
||||
|
||||
// 1. Apply turning from keyboard + mouse.
|
||||
if (input.TurnRight)
|
||||
Yaw -= TurnSpeed * dt;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue