CombatSession.cs
1 using GUNRPG.Application.Combat; 2 using GUNRPG.Core; 3 using GUNRPG.Core.AI; 4 using GUNRPG.Core.Combat; 5 using GUNRPG.Core.Equipment; 6 using GUNRPG.Core.Operators; 7 using GUNRPG.Core.VirtualPet; 8 9 namespace GUNRPG.Application.Sessions; 10 11 /// <summary> 12 /// Aggregates combat state, AI, and operator lifecycle for a single session. 13 /// </summary> 14 public sealed class CombatSession 15 { 16 public Guid Id { get; } 17 public OperatorId OperatorId { get; } // Reference to the operator (does not store progression) 18 public CombatSystemV2 Combat { get; } 19 public SimpleAIV2 Ai { get; } 20 public OperatorManager OperatorManager { get; } 21 public PetState PetState { get; set; } 22 public int EnemyLevel { get; } 23 public int Seed { get; } 24 public SessionPhase Phase { get; private set; } 25 public int TurnNumber { get; private set; } 26 public DateTimeOffset CreatedAt { get; } 27 public DateTimeOffset? CompletedAt { get; private set; } 28 public DateTimeOffset LastActionTimestamp { get; private set; } 29 public bool PostCombatResolved { get; set; } 30 31 public Operator Player => Combat.Player; 32 public Operator Enemy => Combat.Enemy; 33 34 public CombatSession( 35 Guid id, 36 OperatorId operatorId, 37 CombatSystemV2 combat, 38 SimpleAIV2 ai, 39 OperatorManager operatorManager, 40 PetState petState, 41 int enemyLevel, 42 int seed, 43 SessionPhase phase, 44 int turnNumber, 45 DateTimeOffset createdAt, 46 DateTimeOffset? completedAt = null, 47 DateTimeOffset? lastActionTimestamp = null) 48 { 49 Id = id; 50 OperatorId = operatorId; 51 Combat = combat; 52 Ai = ai; 53 OperatorManager = operatorManager; 54 PetState = petState; 55 EnemyLevel = enemyLevel; 56 Seed = seed; 57 Phase = phase; 58 TurnNumber = turnNumber; 59 CreatedAt = createdAt; 60 CompletedAt = completedAt; 61 LastActionTimestamp = lastActionTimestamp ?? createdAt; 62 } 63 64 public static CombatSession CreateDefault(string? playerName = null, int? seed = null, float? startingDistance = null, string? enemyName = null, Guid? id = null, Guid? operatorId = null) 65 { 66 var resolvedSeed = seed ?? Random.Shared.Next(); 67 var name = string.IsNullOrWhiteSpace(playerName) ? "Player" : playerName.Trim(); 68 var foeName = string.IsNullOrWhiteSpace(enemyName) ? "Enemy" : enemyName.Trim(); 69 70 var player = new Operator(name) 71 { 72 EquippedWeapon = WeaponFactory.CreateSokol545(), 73 DistanceToOpponent = startingDistance ?? 15f 74 }; 75 player.CurrentAmmo = player.EquippedWeapon!.MagazineSize; 76 77 var enemy = new Operator(foeName) 78 { 79 EquippedWeapon = WeaponFactory.CreateSturmwolf45(), 80 DistanceToOpponent = startingDistance ?? 15f 81 }; 82 enemy.CurrentAmmo = enemy.EquippedWeapon!.MagazineSize; 83 84 var debugOptions = new CombatDebugOptions { VerboseShotLogs = false }; 85 var combat = new CombatSystemV2(player, enemy, seed: resolvedSeed, debugOptions: debugOptions); 86 var ai = new SimpleAIV2(seed: resolvedSeed); 87 var operatorManager = new OperatorManager(); 88 89 var petState = new PetState(player.Id, 100f, 0f, 0f, 0f, 100f, 0f, 100f, DateTimeOffset.UtcNow); 90 var enemyLevel = Math.Max(0, new Random(resolvedSeed).Next(-2, 3)); 91 var resolvedOperatorId = operatorId.HasValue 92 ? OperatorId.FromGuid(operatorId.Value) 93 : OperatorId.FromGuid(player.Id); 94 95 return new CombatSession( 96 id ?? Guid.NewGuid(), 97 resolvedOperatorId, 98 combat, 99 ai, 100 operatorManager, 101 petState, 102 enemyLevel, 103 resolvedSeed, 104 SessionPhase.Planning, 105 1, 106 DateTimeOffset.UtcNow); 107 } 108 109 public void TransitionTo(SessionPhase nextPhase) 110 { 111 if (!IsValidTransition(Phase, nextPhase)) 112 { 113 throw new InvalidOperationException($"Invalid session phase transition: {Phase} -> {nextPhase}"); 114 } 115 116 Phase = nextPhase; 117 LastActionTimestamp = DateTimeOffset.UtcNow; 118 119 // Record completion time when transitioning to Completed phase 120 if (nextPhase == SessionPhase.Completed && CompletedAt == null) 121 { 122 CompletedAt = DateTimeOffset.UtcNow; 123 } 124 } 125 126 public void AdvanceTurnCounter() 127 { 128 TurnNumber++; 129 LastActionTimestamp = DateTimeOffset.UtcNow; 130 } 131 132 public void RecordAction() 133 { 134 LastActionTimestamp = DateTimeOffset.UtcNow; 135 } 136 137 /// <summary> 138 /// Gets the combat outcome for this session. 139 /// Can only be called after the session has completed. 140 /// </summary> 141 /// <returns>The combat outcome.</returns> 142 /// <exception cref="InvalidOperationException">Thrown if the session is not yet completed.</exception> 143 public CombatOutcome GetOutcome() 144 { 145 if (Phase != SessionPhase.Completed) 146 { 147 throw new InvalidOperationException( 148 $"Cannot produce outcome: session is not completed (current phase: {Phase})"); 149 } 150 151 var player = Player; 152 var enemy = Enemy; 153 154 var operatorDied = !player.IsAlive; 155 var damageTaken = player.MaxHealth - player.Health; 156 157 // Calculate XP based on outcome 158 var isVictory = player.IsAlive && !enemy.IsAlive; 159 160 int xpGained; 161 if (isVictory) 162 { 163 xpGained = 100; // Base XP for victory 164 } 165 else if (!operatorDied) 166 { 167 xpGained = 50; // Partial XP for surviving 168 } 169 else 170 { 171 xpGained = 0; // No XP for death 172 } 173 174 var operatorId = OperatorId; 175 176 // For now, no gear is lost (will be expanded later with actual gear system) 177 var gearLost = Array.Empty<GearId>(); 178 179 return new CombatOutcome( 180 sessionId: Id, 181 operatorId: operatorId, 182 operatorDied: operatorDied, 183 xpGained: xpGained, 184 gearLost: gearLost, 185 isVictory: isVictory, 186 turnsSurvived: TurnNumber, 187 damageTaken: damageTaken, 188 completedAt: CompletedAt ?? DateTimeOffset.UtcNow); 189 } 190 191 private static bool IsValidTransition(SessionPhase current, SessionPhase next) 192 { 193 return (current, next) switch 194 { 195 (SessionPhase.Created, SessionPhase.Planning) => true, 196 (SessionPhase.Planning, SessionPhase.Resolving) => true, 197 (SessionPhase.Resolving, SessionPhase.Planning) => true, 198 (SessionPhase.Planning, SessionPhase.Completed) => true, 199 (SessionPhase.Resolving, SessionPhase.Completed) => true, 200 _ => current == next 201 }; 202 } 203 }