/ GUNRPG.Core / VirtualPet / RestSystem.cs
RestSystem.cs
  1  using GUNRPG.Core.Operators;
  2  
  3  namespace GUNRPG.Core.VirtualPet;
  4  
  5  /// <summary>
  6  /// Manages the virtual pet loop - operator rest, recovery, and readiness.
  7  /// </summary>
  8  public class RestSystem
  9  {
 10      /// <summary>
 11      /// Amount of fatigue gained per combat encounter.
 12      /// </summary>
 13      public float FatiguePerCombat { get; set; } = 20f;
 14  
 15      /// <summary>
 16      /// Fatigue recovery rate per hour of rest.
 17      /// </summary>
 18      public float FatigueRecoveryPerHour { get; set; } = 30f;
 19  
 20      /// <summary>
 21      /// Maximum fatigue before operator cannot deploy.
 22      /// </summary>
 23      public float MaxDeployableFatigue { get; set; } = 80f;
 24  
 25      /// <summary>
 26      /// Checks if an operator is ready for combat.
 27      /// </summary>
 28      public bool IsReadyForCombat(Operator op)
 29      {
 30          return op.Fatigue < MaxDeployableFatigue && op.IsAlive;
 31      }
 32  
 33      /// <summary>
 34      /// Applies fatigue after a combat encounter.
 35      /// </summary>
 36      public void ApplyPostCombatFatigue(Operator op)
 37      {
 38          op.Fatigue = Math.Min(op.MaxFatigue, op.Fatigue + FatiguePerCombat);
 39      }
 40  
 41      /// <summary>
 42      /// Recovers fatigue during rest.
 43      /// </summary>
 44      /// <param name="op">Operator resting</param>
 45      /// <param name="hoursRested">Duration of rest in hours</param>
 46      public void Rest(Operator op, float hoursRested)
 47      {
 48          float fatigueRecovered = FatigueRecoveryPerHour * hoursRested;
 49          op.Fatigue = Math.Max(0, op.Fatigue - fatigueRecovered);
 50          
 51          // Fully restore health during rest
 52          op.Health = op.MaxHealth;
 53          op.Stamina = op.MaxStamina;
 54          op.LastDamageTimeMs = null;
 55      }
 56  
 57      /// <summary>
 58      /// Gets the minimum rest time required for the operator to be combat-ready.
 59      /// </summary>
 60      public float GetMinimumRestHours(Operator op)
 61      {
 62          if (IsReadyForCombat(op))
 63              return 0f;
 64  
 65          float excessFatigue = op.Fatigue - MaxDeployableFatigue;
 66          return excessFatigue / FatigueRecoveryPerHour;
 67      }
 68  
 69      /// <summary>
 70      /// Gets a status summary for the operator.
 71      /// </summary>
 72      public string GetOperatorStatus(Operator op)
 73      {
 74          if (!op.IsAlive)
 75              return "❌ Deceased";
 76  
 77          if (IsReadyForCombat(op))
 78              return "✓ Ready for combat";
 79  
 80          float minRestHours = GetMinimumRestHours(op);
 81          return $"⚠ Too fatigued (needs {minRestHours:F1}h rest)";
 82      }
 83  }
 84  
 85  /// <summary>
 86  /// Manages operator schedule and availability.
 87  /// </summary>
 88  public class OperatorManager
 89  {
 90      private readonly RestSystem _restSystem;
 91      private readonly Dictionary<Guid, DateTime> _restStartTimes;
 92  
 93      public OperatorManager()
 94      {
 95          _restSystem = new RestSystem();
 96          _restStartTimes = new Dictionary<Guid, DateTime>();
 97      }
 98  
 99      /// <summary>
100      /// Sends an operator to rest.
101      /// </summary>
102      public void SendToRest(Operator op)
103      {
104          _restStartTimes[op.Id] = DateTime.UtcNow;
105      }
106  
107      /// <summary>
108      /// Checks if an operator is currently resting.
109      /// </summary>
110      public bool IsResting(Operator op)
111      {
112          return _restStartTimes.ContainsKey(op.Id);
113      }
114  
115      /// <summary>
116      /// Gets the duration an operator has been resting.
117      /// </summary>
118      public TimeSpan GetRestDuration(Operator op)
119      {
120          if (!_restStartTimes.TryGetValue(op.Id, out var startTime))
121              return TimeSpan.Zero;
122  
123          return DateTime.UtcNow - startTime;
124      }
125  
126      /// <summary>
127      /// Wakes an operator from rest, applying fatigue recovery.
128      /// </summary>
129      public void WakeFromRest(Operator op)
130      {
131          if (!_restStartTimes.TryGetValue(op.Id, out var startTime))
132              return;
133  
134          TimeSpan restDuration = DateTime.UtcNow - startTime;
135          float hoursRested = (float)restDuration.TotalHours;
136          
137          _restSystem.Rest(op, hoursRested);
138          _restStartTimes.Remove(op.Id);
139      }
140  
141      /// <summary>
142      /// Prepares an operator for combat deployment.
143      /// Fully restores health and stamina.
144      /// </summary>
145      public bool PrepareForCombat(Operator op)
146      {
147          if (!_restSystem.IsReadyForCombat(op))
148              return false;
149  
150          // Reset combat-scoped state
151          op.Health = op.MaxHealth;
152          op.Stamina = op.MaxStamina;
153          op.LastDamageTimeMs = null;
154          op.MovementState = MovementState.Idle;
155          op.AimState = AimState.Hip;
156          op.WeaponState = WeaponState.Ready;
157          op.CurrentRecoilX = 0;
158          op.CurrentRecoilY = 0;
159          op.RecoilRecoveryStartMs = null;
160          op.BulletsFiredSinceLastReaction = 0;
161          op.MetersMovedSinceLastReaction = 0;
162          
163          if (op.EquippedWeapon != null)
164          {
165              op.CurrentAmmo = op.EquippedWeapon.MagazineSize;
166          }
167  
168          return true;
169      }
170  
171      /// <summary>
172      /// Handles post-combat cleanup.
173      /// </summary>
174      public void CompleteCombat(Operator op, bool survived)
175      {
176          if (survived)
177          {
178              _restSystem.ApplyPostCombatFatigue(op);
179          }
180      }
181  
182      /// <summary>
183      /// Gets operator readiness status.
184      /// </summary>
185      public string GetStatus(Operator op)
186      {
187          if (IsResting(op))
188          {
189              var duration = GetRestDuration(op);
190              return $"💤 Resting ({duration.Hours}h {duration.Minutes}m)";
191          }
192  
193          return _restSystem.GetOperatorStatus(op);
194      }
195  
196      /// <summary>
197      /// Gets the rest system for configuration.
198      /// </summary>
199      public RestSystem RestSystem => _restSystem;
200  }