/ GUNRPG.Application / Mapping / SessionMapping.cs
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  }