SessionMapping.cs
1 using GUNRPG.Application.Combat; 2 using GUNRPG.Application.Dtos; 3 using GUNRPG.Application.Sessions; 4 using GUNRPG.Core.AI; 5 using GUNRPG.Core.Intents; 6 using GUNRPG.Core.Operators; 7 using GUNRPG.Core.VirtualPet; 8 using GUNRPG.Core; 9 using GUNRPG.Core.Combat; 10 using GUNRPG.Core.Weapons; 11 12 namespace GUNRPG.Application.Mapping; 13 14 public static class SessionMapping 15 { 16 public static CombatSessionDto ToDto(CombatSession session) 17 { 18 var battleLog = BattleLogFormatter.FormatEvents( 19 session.Combat.ExecutedEvents, 20 session.Player, 21 session.Enemy); 22 23 return new CombatSessionDto 24 { 25 Id = session.Id, 26 OperatorId = session.OperatorId.Value, 27 Phase = session.Phase, 28 CurrentTimeMs = session.Combat.CurrentTimeMs, 29 Player = ToDto(session.Player), 30 Enemy = ToDto(session.Enemy), 31 Pet = ToDto(session.PetState), 32 EnemyLevel = session.EnemyLevel, 33 TurnNumber = session.TurnNumber, 34 BattleLog = battleLog 35 }; 36 } 37 38 public static PlayerStateDto ToDto(Operator op) 39 { 40 return new PlayerStateDto 41 { 42 Id = op.Id, 43 Name = op.Name, 44 Health = op.Health, 45 MaxHealth = op.MaxHealth, 46 Stamina = op.Stamina, 47 Fatigue = op.Fatigue, 48 SuppressionLevel = op.SuppressionLevel, 49 IsSuppressed = op.IsSuppressed, 50 DistanceToOpponent = op.DistanceToOpponent, 51 CurrentAmmo = op.CurrentAmmo, 52 MagazineSize = op.EquippedWeapon?.MagazineSize, 53 AimState = op.AimState, 54 MovementState = op.MovementState, 55 CurrentMovement = op.CurrentMovement, 56 CurrentDirection = op.CurrentDirection, 57 CurrentCover = op.CurrentCover, 58 IsMoving = op.IsMoving, 59 IsAlive = op.IsAlive 60 }; 61 } 62 63 public static PetStateDto ToDto(PetState pet) 64 { 65 return new PetStateDto 66 { 67 Health = pet.Health, 68 Fatigue = pet.Fatigue, 69 Injury = pet.Injury, 70 Stress = pet.Stress, 71 Morale = pet.Morale, 72 Hunger = pet.Hunger, 73 Hydration = pet.Hydration, 74 LastUpdated = pet.LastUpdated 75 }; 76 } 77 78 public static SimultaneousIntents ToDomainIntent(Guid operatorId, IntentDto dto) 79 { 80 return new SimultaneousIntents(operatorId) 81 { 82 Primary = dto.Primary ?? PrimaryAction.None, 83 Movement = dto.Movement ?? MovementAction.Stand, 84 Stance = dto.Stance ?? StanceAction.None, 85 Cover = dto.Cover ?? CoverAction.None, 86 CancelMovement = dto.CancelMovement 87 }; 88 } 89 90 public static CombatSessionSnapshot ToSnapshot(CombatSession session) 91 { 92 var (playerIntents, enemyIntents) = session.Combat.GetPendingIntents(); 93 var (randomSeed, randomCalls) = session.Combat.GetRandomState(); 94 95 return new CombatSessionSnapshot 96 { 97 Id = session.Id, 98 OperatorId = session.OperatorId.Value, 99 Phase = session.Phase, 100 TurnNumber = session.TurnNumber, 101 EnemyLevel = session.EnemyLevel, 102 Seed = session.Seed, 103 PostCombatResolved = session.PostCombatResolved, 104 CreatedAt = session.CreatedAt, 105 CompletedAt = session.CompletedAt, 106 LastActionTimestamp = session.LastActionTimestamp, 107 Combat = new CombatStateSnapshot 108 { 109 Phase = session.Combat.Phase, 110 CurrentTimeMs = session.Combat.CurrentTimeMs, 111 PlayerIntents = ToSnapshot(playerIntents), 112 EnemyIntents = ToSnapshot(enemyIntents), 113 RandomState = new RandomStateSnapshot { Seed = randomSeed, CallCount = randomCalls } 114 }, 115 Player = ToSnapshot(session.Player), 116 Enemy = ToSnapshot(session.Enemy), 117 Pet = ToSnapshot(session.PetState) 118 }; 119 } 120 121 public static CombatSession FromSnapshot(CombatSessionSnapshot snapshot) 122 { 123 var player = ToOperator(snapshot.Player); 124 var enemy = ToOperator(snapshot.Enemy); 125 126 var combat = CombatSystemV2.FromState( 127 player, 128 enemy, 129 snapshot.Combat.Phase, 130 snapshot.Combat.CurrentTimeMs, 131 ToDomainIntent(snapshot.Combat.PlayerIntents), 132 ToDomainIntent(snapshot.Combat.EnemyIntents), 133 snapshot.Combat.RandomState.Seed, 134 snapshot.Combat.RandomState.CallCount); 135 136 var ai = new SimpleAIV2(seed: snapshot.Seed); 137 var operatorManager = new OperatorManager(); 138 var petState = new PetState( 139 snapshot.Pet.OperatorId, 140 snapshot.Pet.Health, 141 snapshot.Pet.Fatigue, 142 snapshot.Pet.Injury, 143 snapshot.Pet.Stress, 144 snapshot.Pet.Morale, 145 snapshot.Pet.Hunger, 146 snapshot.Pet.Hydration, 147 snapshot.Pet.LastUpdated); 148 149 // Backwards compatibility: fallback to Player.Id if OperatorId is empty 150 var operatorId = snapshot.OperatorId != Guid.Empty 151 ? OperatorId.FromGuid(snapshot.OperatorId) 152 : OperatorId.FromGuid(snapshot.Player.Id); 153 154 return new CombatSession( 155 snapshot.Id, 156 operatorId, 157 combat, 158 ai, 159 operatorManager, 160 petState, 161 snapshot.EnemyLevel, 162 snapshot.Seed, 163 snapshot.Phase, 164 snapshot.TurnNumber, 165 snapshot.CreatedAt, 166 snapshot.CompletedAt, 167 snapshot.LastActionTimestamp ?? snapshot.CreatedAt) // Fallback to CreatedAt for old snapshots 168 { 169 PostCombatResolved = snapshot.PostCombatResolved 170 }; 171 } 172 173 private static OperatorSnapshot ToSnapshot(Operator op) 174 { 175 return new OperatorSnapshot 176 { 177 Id = op.Id, 178 Name = op.Name, 179 Health = op.Health, 180 MaxHealth = op.MaxHealth, 181 Stamina = op.Stamina, 182 MaxStamina = op.MaxStamina, 183 Fatigue = op.Fatigue, 184 MaxFatigue = op.MaxFatigue, 185 MovementState = op.MovementState, 186 AimState = op.AimState, 187 WeaponState = op.WeaponState, 188 CurrentMovement = op.CurrentMovement, 189 CurrentCover = op.CurrentCover, 190 CurrentDirection = op.CurrentDirection, 191 MovementEndTimeMs = op.MovementEndTimeMs, 192 EquippedWeaponName = op.EquippedWeapon?.Name, 193 CurrentAmmo = op.CurrentAmmo, 194 DistanceToOpponent = op.DistanceToOpponent, 195 LastDamageTimeMs = op.LastDamageTimeMs, 196 HealthRegenDelayMs = op.HealthRegenDelayMs, 197 HealthRegenRate = op.HealthRegenRate, 198 StaminaRegenRate = op.StaminaRegenRate, 199 SprintStaminaDrainRate = op.SprintStaminaDrainRate, 200 SlideStaminaCost = op.SlideStaminaCost, 201 WalkSpeed = op.WalkSpeed, 202 SprintSpeed = op.SprintSpeed, 203 SlideDistance = op.SlideDistance, 204 SlideDurationMs = op.SlideDurationMs, 205 CurrentRecoilX = op.CurrentRecoilX, 206 CurrentRecoilY = op.CurrentRecoilY, 207 RecoilRecoveryStartMs = op.RecoilRecoveryStartMs, 208 RecoilRecoveryRate = op.RecoilRecoveryRate, 209 Accuracy = op.Accuracy, 210 AccuracyProficiency = op.AccuracyProficiency, 211 ResponseProficiency = op.ResponseProficiency, 212 ShotsFiredCount = op.ShotsFiredCount, 213 BulletsFiredSinceLastReaction = op.BulletsFiredSinceLastReaction, 214 MetersMovedSinceLastReaction = op.MetersMovedSinceLastReaction, 215 ADSTransitionStartMs = op.ADSTransitionStartMs, 216 ADSTransitionDurationMs = op.ADSTransitionDurationMs, 217 IsActivelyFiring = op.IsActivelyFiring, 218 IsCoverTransitioning = op.IsCoverTransitioning, 219 CoverTransitionFromState = op.CoverTransitionFromState, 220 CoverTransitionToState = op.CoverTransitionToState, 221 CoverTransitionStartMs = op.CoverTransitionStartMs, 222 CoverTransitionEndMs = op.CoverTransitionEndMs, 223 SuppressionLevel = op.SuppressionLevel, 224 SuppressionDecayStartMs = op.SuppressionDecayStartMs, 225 LastSuppressionApplicationMs = op.LastSuppressionApplicationMs, 226 FlinchSeverity = op.FlinchSeverity, 227 FlinchShotsRemaining = op.FlinchShotsRemaining, 228 FlinchDurationShots = op.FlinchDurationShots, 229 RecognitionDelayEndMs = op.RecognitionDelayEndMs, 230 RecognitionTargetId = op.RecognitionTargetId, 231 LastTargetVisibleMs = op.LastTargetVisibleMs, 232 RecognitionStartMs = op.RecognitionStartMs, 233 WasTargetVisible = op.WasTargetVisible 234 }; 235 } 236 237 private static Operator ToOperator(OperatorSnapshot snapshot) 238 { 239 var op = new Operator(snapshot.Name, snapshot.Id) 240 { 241 Health = snapshot.Health, 242 MaxHealth = snapshot.MaxHealth, 243 Stamina = snapshot.Stamina, 244 MaxStamina = snapshot.MaxStamina, 245 Fatigue = snapshot.Fatigue, 246 MaxFatigue = snapshot.MaxFatigue, 247 MovementState = snapshot.MovementState, 248 AimState = snapshot.AimState, 249 WeaponState = snapshot.WeaponState, 250 CurrentMovement = snapshot.CurrentMovement, 251 CurrentCover = snapshot.CurrentCover, 252 CurrentDirection = snapshot.CurrentDirection, 253 MovementEndTimeMs = snapshot.MovementEndTimeMs, 254 CurrentAmmo = snapshot.CurrentAmmo, 255 DistanceToOpponent = snapshot.DistanceToOpponent, 256 LastDamageTimeMs = snapshot.LastDamageTimeMs, 257 HealthRegenDelayMs = snapshot.HealthRegenDelayMs, 258 HealthRegenRate = snapshot.HealthRegenRate, 259 StaminaRegenRate = snapshot.StaminaRegenRate, 260 SprintStaminaDrainRate = snapshot.SprintStaminaDrainRate, 261 SlideStaminaCost = snapshot.SlideStaminaCost, 262 WalkSpeed = snapshot.WalkSpeed, 263 SprintSpeed = snapshot.SprintSpeed, 264 SlideDistance = snapshot.SlideDistance, 265 SlideDurationMs = snapshot.SlideDurationMs, 266 CurrentRecoilX = snapshot.CurrentRecoilX, 267 CurrentRecoilY = snapshot.CurrentRecoilY, 268 RecoilRecoveryStartMs = snapshot.RecoilRecoveryStartMs, 269 RecoilRecoveryRate = snapshot.RecoilRecoveryRate, 270 Accuracy = snapshot.Accuracy, 271 AccuracyProficiency = snapshot.AccuracyProficiency, 272 ResponseProficiency = snapshot.ResponseProficiency, 273 BulletsFiredSinceLastReaction = snapshot.BulletsFiredSinceLastReaction, 274 MetersMovedSinceLastReaction = snapshot.MetersMovedSinceLastReaction, 275 ADSTransitionStartMs = snapshot.ADSTransitionStartMs, 276 ADSTransitionDurationMs = snapshot.ADSTransitionDurationMs, 277 IsActivelyFiring = snapshot.IsActivelyFiring, 278 IsCoverTransitioning = snapshot.IsCoverTransitioning, 279 CoverTransitionFromState = snapshot.CoverTransitionFromState, 280 CoverTransitionToState = snapshot.CoverTransitionToState, 281 CoverTransitionStartMs = snapshot.CoverTransitionStartMs, 282 CoverTransitionEndMs = snapshot.CoverTransitionEndMs, 283 RecognitionDelayEndMs = snapshot.RecognitionDelayEndMs, 284 RecognitionTargetId = snapshot.RecognitionTargetId, 285 LastTargetVisibleMs = snapshot.LastTargetVisibleMs, 286 RecognitionStartMs = snapshot.RecognitionStartMs, 287 WasTargetVisible = snapshot.WasTargetVisible 288 }; 289 290 var weapon = CreateWeapon(snapshot.EquippedWeaponName); 291 if (weapon != null) 292 { 293 op.EquippedWeapon = weapon; 294 op.CurrentAmmo = snapshot.CurrentAmmo; 295 } 296 297 op.RestoreSuppression(snapshot.SuppressionLevel, snapshot.LastSuppressionApplicationMs, snapshot.SuppressionDecayStartMs); 298 op.RestoreFlinch(snapshot.FlinchSeverity, snapshot.FlinchShotsRemaining, snapshot.FlinchDurationShots); 299 op.RestoreShotsFired(snapshot.ShotsFiredCount); 300 return op; 301 } 302 303 private static IntentSnapshot? ToSnapshot(SimultaneousIntents? intents) 304 { 305 if (intents == null) 306 return null; 307 308 return new IntentSnapshot 309 { 310 OperatorId = intents.OperatorId, 311 Primary = intents.Primary, 312 Movement = intents.Movement, 313 Stance = intents.Stance, 314 Cover = intents.Cover, 315 CancelMovement = intents.CancelMovement, 316 SubmittedAtMs = intents.SubmittedAtMs 317 }; 318 } 319 320 private static SimultaneousIntents? ToDomainIntent(IntentSnapshot? snapshot) 321 { 322 if (snapshot == null) 323 return null; 324 325 return new SimultaneousIntents(snapshot.OperatorId) 326 { 327 Primary = snapshot.Primary, 328 Movement = snapshot.Movement, 329 Stance = snapshot.Stance, 330 Cover = snapshot.Cover, 331 CancelMovement = snapshot.CancelMovement, 332 SubmittedAtMs = snapshot.SubmittedAtMs 333 }; 334 } 335 336 private static PetStateSnapshot ToSnapshot(PetState pet) 337 { 338 return new PetStateSnapshot 339 { 340 OperatorId = pet.OperatorId, 341 Health = pet.Health, 342 Fatigue = pet.Fatigue, 343 Injury = pet.Injury, 344 Stress = pet.Stress, 345 Morale = pet.Morale, 346 Hunger = pet.Hunger, 347 Hydration = pet.Hydration, 348 LastUpdated = pet.LastUpdated 349 }; 350 } 351 352 private static Weapon? CreateWeapon(string? name) 353 { 354 return name switch 355 { 356 "SOKOL 545" => WeaponFactory.CreateSokol545(), 357 "STURMWOLF 45" => WeaponFactory.CreateSturmwolf45(), 358 "M15 MOD 0" => WeaponFactory.CreateM15Mod0(), 359 null or "" => null, 360 _ => new Weapon(name) 361 }; 362 } 363 }