diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index bfdaa69..e96269c 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -32,7 +32,8 @@ public readonly record struct MovementResult( float? ForwardSpeed, float? SidestepSpeed, float? TurnSpeed, - float? JumpExtent = null); // non-null when a jump was triggered this frame + float? JumpExtent = null, // non-null when a jump was triggered this frame + Vector3? JumpVelocity = null); // world-space launch velocity (sent in jump packet) /// /// Portal-space state for the player movement controller. @@ -259,6 +260,7 @@ public sealed class PlayerMovementController // Release to execute: jump(extent) validates + sets JumpExtent, // then LeaveGround() applies the scaled velocity via get_leave_ground_velocity. float? outJumpExtent = null; + Vector3? outJumpVelocity = null; if (input.Jump && _body.OnWalkable) { @@ -278,6 +280,7 @@ public sealed class PlayerMovementController { _motion.LeaveGround(); outJumpExtent = _jumpExtent; + outJumpVelocity = _body.Velocity; // capture after LeaveGround applies it } _jumpCharging = false; _jumpExtent = 0f; @@ -411,6 +414,7 @@ public sealed class PlayerMovementController ForwardSpeed: outForwardSpeed, SidestepSpeed: outSidestepSpeed, TurnSpeed: outTurnSpeed, - JumpExtent: outJumpExtent); + JumpExtent: outJumpExtent, + JumpVelocity: outJumpVelocity); } } diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 981070b..d425bcb 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1936,11 +1936,18 @@ public sealed class GameWindow : IDisposable _liveSession.SendGameAction(body); } - if (result.JumpExtent.HasValue) + if (result.JumpExtent.HasValue && result.JumpVelocity.HasValue) { - // TODO: send jump packet to server (format needs research from holtburger). - // Local jump physics work without server acknowledgment for now. - Console.WriteLine($"jump: extent={result.JumpExtent.Value:F2}"); + var seq = _liveSession.NextGameActionSequence(); + var jumpBody = AcDream.Core.Net.Messages.JumpAction.Build( + gameActionSequence: seq, + extent: result.JumpExtent.Value, + velocity: result.JumpVelocity.Value, + instanceSequence: _liveSession.InstanceSequence, + serverControlSequence: _liveSession.ServerControlSequence, + teleportSequence: _liveSession.TeleportSequence, + forcePositionSequence: _liveSession.ForcePositionSequence); + _liveSession.SendGameAction(jumpBody); } } diff --git a/src/AcDream.Core.Net/Messages/JumpAction.cs b/src/AcDream.Core.Net/Messages/JumpAction.cs new file mode 100644 index 0000000..d5bc6d7 --- /dev/null +++ b/src/AcDream.Core.Net/Messages/JumpAction.cs @@ -0,0 +1,57 @@ +using System.Numerics; +using AcDream.Core.Net.Packets; + +namespace AcDream.Core.Net.Messages; + +/// +/// Outbound GameAction(Jump) message — opcode 0xF61B. +/// Sent when the player releases the spacebar to jump. The server uses +/// the extent + velocity to validate the jump and replicate it to nearby +/// clients. +/// +/// Wire layout (from holtburger JumpActionData::pack): +/// u32 0xF7B1 (GameAction envelope) +/// u32 sequence +/// u32 0xF61B (Jump sub-opcode) +/// f32 extent (0.0–1.0 charge power) +/// f32 velocity.x, f32 velocity.y, f32 velocity.z +/// u16 instanceSequence +/// u16 serverControlSequence +/// u16 teleportSequence +/// u16 forcePositionSequence +/// u32 objectGuid (0 for normal jump) +/// u32 spellId (0 for normal jump) +/// +public static class JumpAction +{ + public const uint GameActionOpcode = 0xF7B1u; + public const uint JumpOpcode = 0xF61Bu; + + public static byte[] Build( + uint gameActionSequence, + float extent, + Vector3 velocity, + ushort instanceSequence, + ushort serverControlSequence, + ushort teleportSequence, + ushort forcePositionSequence) + { + var w = new PacketWriter(48); + + w.WriteUInt32(GameActionOpcode); + w.WriteUInt32(gameActionSequence); + w.WriteUInt32(JumpOpcode); + w.WriteFloat(extent); + w.WriteFloat(velocity.X); + w.WriteFloat(velocity.Y); + w.WriteFloat(velocity.Z); + w.WriteUInt16(instanceSequence); + w.WriteUInt16(serverControlSequence); + w.WriteUInt16(teleportSequence); + w.WriteUInt16(forcePositionSequence); + w.WriteUInt32(0); // objectGuid — 0 for normal jump + w.WriteUInt32(0); // spellId — 0 for normal jump + + return w.ToArray(); + } +}