From b7c003f227f7be4b7397e723dd3583b25c08f5d3 Mon Sep 17 00:00:00 2001 From: SepComet <202308010230@stu.csust.edu.cn> Date: Tue, 7 Apr 2026 20:42:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20spec=20=E4=B8=8E?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=9A=84=E5=A4=9A=E5=A4=84=E5=B7=AE=E5=BC=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CombatEvent Hit 消息的 Damage 字段改为传递 configuration.DamagePerShot - SyncSequenceTracker MoveInput streamKey 改为只按 playerId 追踪 - 登录成功后新增 AuthoritativeCombatState bootstrap 逻辑 - 更新 network-gameplay-message-types spec 字段命名以匹配实际 proto 定义 --- .../NetworkApplication/SyncSequenceTracker.cs | 2 +- .../ServerAuthoritativeCombatCoordinator.cs | 25 ++++++++++++++++++- .../Network/NetworkHost/ServerNetworkHost.cs | 16 ++++++++++++ .../network-gameplay-message-types/spec.md | 10 ++++---- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Assets/Scripts/Network/NetworkApplication/SyncSequenceTracker.cs b/Assets/Scripts/Network/NetworkApplication/SyncSequenceTracker.cs index 5aa0404..3006472 100644 --- a/Assets/Scripts/Network/NetworkApplication/SyncSequenceTracker.cs +++ b/Assets/Scripts/Network/NetworkApplication/SyncSequenceTracker.cs @@ -41,7 +41,7 @@ namespace Network.NetworkApplication case MessageType.MoveInput: { var input = MoveInput.Parser.ParseFrom(payload); - streamKey = $"input:{Normalize(sender)}:{input.PlayerId}"; + streamKey = $"input:{input.PlayerId}"; sequence = input.Tick; return true; } diff --git a/Assets/Scripts/Network/NetworkHost/ServerAuthoritativeCombatCoordinator.cs b/Assets/Scripts/Network/NetworkHost/ServerAuthoritativeCombatCoordinator.cs index 3fab8ff..fde9bc0 100644 --- a/Assets/Scripts/Network/NetworkHost/ServerAuthoritativeCombatCoordinator.cs +++ b/Assets/Scripts/Network/NetworkHost/ServerAuthoritativeCombatCoordinator.cs @@ -42,6 +42,29 @@ namespace Network.NetworkHost } } + public void BootstrapState(IPEndPoint remoteEndPoint, string playerId, int hp, bool isDead) + { + if (remoteEndPoint == null || string.IsNullOrWhiteSpace(playerId)) + { + return; + } + + var key = Normalize(remoteEndPoint).ToString(); + lock (gate) + { + if (statesByPeer.ContainsKey(key)) + { + return; + } + + statesByPeer.Add(key, new ServerAuthoritativeCombatState( + Normalize(remoteEndPoint), + playerId, + hp, + isDead)); + } + } + public Task HandleShootInputAsync(byte[] payload, IPEndPoint sender) { if (payload == null || sender == null) @@ -95,7 +118,7 @@ namespace Network.NetworkHost EventType = CombatEventType.Hit, AttackerId = attackerState.PlayerId, TargetId = targetState.PlayerId, - Damage = 0, + Damage = configuration.DamagePerShot, HitPosition = hitPosition }, MessageType.CombatEvent); diff --git a/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs b/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs index 947e259..7517944 100644 --- a/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs +++ b/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs @@ -284,6 +284,7 @@ namespace Network.NetworkHost RememberPlayerId(remoteEndPoint, playerId); SessionCoordinator.NotifyLoginSucceeded(remoteEndPoint); BootstrapAuthoritativeMovementState(remoteEndPoint, speed); + BootstrapAuthoritativeCombatState(remoteEndPoint, playerId); PublishMetricsSessionSnapshot(remoteEndPoint); } @@ -382,6 +383,21 @@ namespace Network.NetworkHost authoritativeMovementCoordinator.EnsureState(remoteEndPoint, playerId, speed, out _); } + private void BootstrapAuthoritativeCombatState(IPEndPoint remoteEndPoint, string playerId) + { + if (!TryGetKnownPlayerId(remoteEndPoint, out var resolvedPlayerId)) + { + return; + } + + if (!authoritativeMovementCoordinator.TryGetState(remoteEndPoint, out var movementState)) + { + return; + } + + authoritativeCombatCoordinator.BootstrapState(remoteEndPoint, resolvedPlayerId, movementState.Hp, movementState.IsDead); + } + private void RememberPlayerId(IPEndPoint remoteEndPoint, string playerId) { if (remoteEndPoint == null || string.IsNullOrWhiteSpace(playerId)) diff --git a/openspec/specs/network-gameplay-message-types/spec.md b/openspec/specs/network-gameplay-message-types/spec.md index b165fae..5ef905a 100644 --- a/openspec/specs/network-gameplay-message-types/spec.md +++ b/openspec/specs/network-gameplay-message-types/spec.md @@ -17,26 +17,26 @@ The repository SHALL keep the source protobuf schema that defines gameplay netwo - **THEN** the checked-in generated code matches the schema contract used by client and server hosts ### Requirement: Gameplay messages expose explicit MVP payload fields -The shared networking contract SHALL define the MVP payload fields for gameplay messages explicitly in the source protobuf schema and generated C# messages. `MoveInput` MUST expose `playerId`, `tick`, `moveX`, and `moveY`; `ShootInput` MUST expose `playerId`, `tick`, `dirX`, `dirY`, and an optional `targetId`; `PlayerState` MUST expose `playerId`, `tick`, `acknowledgedMoveTick`, `position`, `rotation`, `hp`, and an optional `velocity`; `CombatEvent` MUST expose `tick`, `eventType`, `attackerId`, `targetId`, `damage`, and an optional `hitPosition`. The shared contract MUST also provide `CombatEventType` so combat results use explicit event categories rather than ad hoc integer payload conventions. +The shared networking contract SHALL define the MVP payload fields for gameplay messages explicitly in the source protobuf schema and generated C# messages. `MoveInput` MUST expose `player_id`, `tick`, `turn_input`, and `throttle_input`; `ShootInput` MUST expose `player_id`, `tick`, `dir_x`, `dir_y`, and an optional `target_id`; `PlayerState` MUST expose `player_id`, `tick`, `acknowledged_move_tick`, `position`, `rotation`, `hp`, and `velocity`; `CombatEvent` MUST expose `tick`, `event_type`, `attacker_id`, `target_id`, `damage`, and an optional `hit_position`. The shared contract MUST also provide `CombatEventType` so combat results use explicit event categories rather than ad hoc integer payload conventions. #### Scenario: Movement input carries explicit movement fields - **WHEN** client or server code constructs or parses `MoveInput` -- **THEN** the message exposes `playerId`, `tick`, `moveX`, and `moveY` +- **THEN** the message exposes `player_id`, `tick`, `turn_input`, and `throttle_input` - **THEN** movement intent does not rely on an overloaded payload extension #### Scenario: Shooting input carries explicit aim fields - **WHEN** client or server code constructs or parses `ShootInput` -- **THEN** the message exposes `playerId`, `tick`, `dirX`, `dirY`, and `targetId` +- **THEN** the message exposes `player_id`, `tick`, `dir_x`, `dir_y`, and `target_id` - **THEN** shooting direction and optional target selection are represented directly in the message contract #### Scenario: Authoritative player state carries explicit gameplay state fields - **WHEN** client or server code constructs or parses `PlayerState` -- **THEN** the message exposes `playerId`, `tick`, `acknowledgedMoveTick`, `position`, `rotation`, `hp`, and `velocity` +- **THEN** the message exposes `player_id`, `tick`, `acknowledged_move_tick`, `position`, `rotation`, `hp`, and `velocity` - **THEN** snapshot ordering and acknowledged-input reconciliation are both expressed without ad hoc payload extensions or overloaded tick semantics #### Scenario: Combat events carry explicit result fields and event categories - **WHEN** client or server code constructs or parses `CombatEvent` -- **THEN** the message exposes `tick`, `eventType`, `attackerId`, `targetId`, `damage`, and `hitPosition` +- **THEN** the message exposes `tick`, `event_type`, `attacker_id`, `target_id`, `damage`, and `hit_position` - **THEN** `CombatEventType` provides explicit combat-result categories for interpreting that event payload ### Requirement: Client gameplay actions use split gameplay messages directly