DefaultGameEngine.cs
1 using GUNRPG.Core.Intents; 2 3 namespace GUNRPG.Application.Distributed; 4 5 /// <summary> 6 /// Deterministic game engine for the distributed lockstep authority. 7 /// Maintains a lightweight state model that tracks operator actions for hash verification. 8 /// Used by both <see cref="LocalGameAuthority"/> and <see cref="DistributedAuthority"/> 9 /// to ensure all nodes agree on the action sequence. 10 /// <para> 11 /// This engine mirrors the intent-level state changes from the existing combat system 12 /// (<see cref="Sessions.CombatSessionService"/>). The actual combat resolution (damage, AI, 13 /// phases) is handled by <see cref="Sessions.CombatSessionService"/> and 14 /// <see cref="Combat.DeterministicCombatEngine"/>; this engine provides the deterministic 15 /// action ledger used for P2P state consistency verification. 16 /// </para> 17 /// </summary> 18 public sealed class DefaultGameEngine : IDeterministicGameEngine 19 { 20 public GameStateDto Step(GameStateDto state, PlayerActionDto action) 21 { 22 var operators = state.Operators 23 .Select(op => op.OperatorId == action.OperatorId ? ApplyActionToSnapshot(op, action) : CloneSnapshot(op)) 24 .ToList(); 25 26 // If the operator doesn't exist yet, create and apply 27 if (!operators.Any(op => op.OperatorId == action.OperatorId)) 28 { 29 var newOp = new GameStateDto.OperatorSnapshot 30 { 31 OperatorId = action.OperatorId, 32 Name = $"Operator-{action.OperatorId.ToString()[..8]}", 33 CurrentHealth = 100f, 34 MaxHealth = 100f, 35 EquippedWeaponName = "Default", 36 UnlockedPerks = new List<string>() 37 }; 38 operators.Add(ApplyActionToSnapshot(newOp, action)); 39 } 40 41 return new GameStateDto 42 { 43 ActionCount = state.ActionCount + 1, 44 Operators = operators.OrderBy(op => op.OperatorId).ToList() 45 }; 46 } 47 48 private static GameStateDto.OperatorSnapshot ApplyActionToSnapshot( 49 GameStateDto.OperatorSnapshot snapshot, PlayerActionDto action) 50 { 51 var health = snapshot.CurrentHealth; 52 var xp = snapshot.TotalXp; 53 54 // Mirror intent-level state changes from the combat system 55 if (action.Primary == PrimaryAction.Fire) 56 { 57 xp += 10; 58 } 59 60 if (action.Primary == PrimaryAction.Reload) 61 { 62 xp += 1; 63 } 64 65 return new GameStateDto.OperatorSnapshot 66 { 67 OperatorId = snapshot.OperatorId, 68 Name = snapshot.Name, 69 TotalXp = xp, 70 CurrentHealth = health, 71 MaxHealth = snapshot.MaxHealth, 72 EquippedWeaponName = snapshot.EquippedWeaponName, 73 UnlockedPerks = snapshot.UnlockedPerks.ToList(), 74 ExfilStreak = snapshot.ExfilStreak, 75 IsDead = snapshot.IsDead 76 }; 77 } 78 79 private static GameStateDto.OperatorSnapshot CloneSnapshot(GameStateDto.OperatorSnapshot snapshot) 80 { 81 return new GameStateDto.OperatorSnapshot 82 { 83 OperatorId = snapshot.OperatorId, 84 Name = snapshot.Name, 85 TotalXp = snapshot.TotalXp, 86 CurrentHealth = snapshot.CurrentHealth, 87 MaxHealth = snapshot.MaxHealth, 88 EquippedWeaponName = snapshot.EquippedWeaponName, 89 UnlockedPerks = snapshot.UnlockedPerks.ToList(), 90 ExfilStreak = snapshot.ExfilStreak, 91 IsDead = snapshot.IsDead 92 }; 93 } 94 }