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
|
|
@ -92,6 +92,26 @@ public sealed class WorldSession : IDisposable
|
|||
/// </summary>
|
||||
public event Action<EntityPositionUpdate>? PositionUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the server sends a PlayerTeleport (0xF751) game message,
|
||||
/// signalling that the player is entering portal space. The uint payload
|
||||
/// is the teleport sequence number parsed from the message body (u16,
|
||||
/// aligned to 4 bytes — per holtburger's teleport.rs wire layout).
|
||||
/// Subscribers should freeze movement input until the destination
|
||||
/// UpdatePosition arrives.
|
||||
/// </summary>
|
||||
public event Action<uint>? TeleportStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Allow re-sending LoginComplete after a portal teleport. The normal
|
||||
/// _loginCompleteSent latch prevents duplicate sends on the initial spawn
|
||||
/// path; this method resets it so the teleport completion path can send
|
||||
/// another LoginComplete to tell the server the client has finished loading
|
||||
/// the destination cell. Pattern from holtburger's PlayerTeleport handler
|
||||
/// (client/messages.rs line 434-440: call send_login_complete on teleport).
|
||||
/// </summary>
|
||||
public void ResetLoginComplete() => _loginCompleteSent = false;
|
||||
|
||||
/// <summary>Raised every time the state machine transitions.</summary>
|
||||
public event Action<State>? StateChanged;
|
||||
|
||||
|
|
@ -426,6 +446,23 @@ public sealed class WorldSession : IDisposable
|
|||
posUpdate.Value.Velocity));
|
||||
}
|
||||
}
|
||||
else if (op == 0xF751u) // PlayerTeleport — server is moving us through a portal
|
||||
{
|
||||
// Phase B.3: holtburger opcodes.rs confirms 0xF751 is the
|
||||
// PlayerTeleport standalone GameMessage (NOT wrapped in 0xF7B0).
|
||||
// Wire layout (teleport.rs): u16 teleport_sequence, then
|
||||
// aligned to 4 bytes. Per holtburger's client handler, the
|
||||
// correct response is send_login_complete() at the destination.
|
||||
// Here we fire TeleportStarted so GameWindow can freeze
|
||||
// movement; the LoginComplete is sent from GameWindow once
|
||||
// the destination UpdatePosition is received and the player
|
||||
// has been snapped to the new cell.
|
||||
uint sequence = 0;
|
||||
if (body.Length >= 6)
|
||||
sequence = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(
|
||||
body.AsSpan(4, 2));
|
||||
TeleportStarted?.Invoke(sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue