/ GUNRPG.Core / Intents / Intent.cs
Intent.cs
  1  namespace GUNRPG.Core.Intents;
  2  
  3  /// <summary>
  4  /// Types of intents that can be submitted.
  5  /// </summary>
  6  public enum IntentType
  7  {
  8      // Combat
  9      FireWeapon,
 10      Reload,
 11      
 12      // Movement (legacy directional)
 13      WalkToward,
 14      WalkAway,
 15      SprintToward,
 16      SprintAway,
 17      SlideToward,
 18      SlideAway,
 19      
 20      // Movement (new state-based)
 21      Walk,
 22      Sprint,
 23      Crouch,
 24      EnterCover,
 25      ExitCover,
 26      CancelMovement,
 27      
 28      // Utility
 29      EnterADS,
 30      ExitADS,
 31      Stop
 32  }
 33  
 34  /// <summary>
 35  /// Base class for all intents.
 36  /// An intent is a declarative statement of future action that schedules events.
 37  /// </summary>
 38  public abstract class Intent
 39  {
 40      public Guid OperatorId { get; set; }
 41      public IntentType Type { get; set; }
 42      public long SubmittedAtMs { get; set; }
 43  
 44      protected Intent(Guid operatorId, IntentType type)
 45      {
 46          OperatorId = operatorId;
 47          Type = type;
 48      }
 49  
 50      /// <summary>
 51      /// Validates if this intent can be executed given the current operator state.
 52      /// </summary>
 53      public abstract (bool isValid, string? errorMessage) Validate(Operators.Operator op);
 54  }
 55  
 56  /// <summary>
 57  /// Intent to fire weapon.
 58  /// </summary>
 59  public class FireWeaponIntent : Intent
 60  {
 61      public bool ContinuousFire { get; set; } // For full-auto
 62  
 63      public FireWeaponIntent(Guid operatorId) : base(operatorId, IntentType.FireWeapon)
 64      {
 65          ContinuousFire = true; // Default to full-auto
 66      }
 67  
 68      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
 69      {
 70          if (op.WeaponState == Operators.WeaponState.Reloading)
 71              return (false, "Cannot fire: weapon is reloading");
 72          
 73          if (op.WeaponState == Operators.WeaponState.Jammed)
 74              return (false, "Cannot fire: weapon is jammed");
 75          
 76          if (op.CurrentAmmo <= 0)
 77              return (false, "Cannot fire: no ammo");
 78          
 79          if (op.EquippedWeapon == null)
 80              return (false, "Cannot fire: no weapon equipped");
 81          
 82          return (true, null);
 83      }
 84  }
 85  
 86  /// <summary>
 87  /// Intent to reload weapon.
 88  /// </summary>
 89  public class ReloadIntent : Intent
 90  {
 91      public ReloadIntent(Guid operatorId) : base(operatorId, IntentType.Reload)
 92      {
 93      }
 94  
 95      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
 96      {
 97          if (op.WeaponState == Operators.WeaponState.Reloading)
 98              return (false, "Already reloading");
 99          
100          if (op.EquippedWeapon == null)
101              return (false, "No weapon equipped");
102          
103          if (op.CurrentAmmo >= op.EquippedWeapon.MagazineSize)
104              return (false, "Magazine is full");
105          
106          return (true, null);
107      }
108  }
109  
110  /// <summary>
111  /// Intent to enter ADS.
112  /// </summary>
113  public class EnterADSIntent : Intent
114  {
115      public EnterADSIntent(Guid operatorId) : base(operatorId, IntentType.EnterADS)
116      {
117      }
118  
119      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
120      {
121          if (op.AimState == Operators.AimState.ADS)
122              return (false, "Already in ADS");
123          
124          if (op.MovementState == Operators.MovementState.Sprinting)
125              return (false, "Cannot ADS while sprinting");
126          
127          if (op.MovementState == Operators.MovementState.Sliding)
128              return (false, "Cannot ADS while sliding");
129          
130          return (true, null);
131      }
132  }
133  
134  /// <summary>
135  /// Intent to exit ADS.
136  /// </summary>
137  public class ExitADSIntent : Intent
138  {
139      public ExitADSIntent(Guid operatorId) : base(operatorId, IntentType.ExitADS)
140      {
141      }
142  
143      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
144      {
145          if (op.AimState == Operators.AimState.Hip)
146              return (false, "Already in hip-fire");
147          
148          return (true, null);
149      }
150  }
151  
152  /// <summary>
153  /// Base class for movement intents.
154  /// </summary>
155  public abstract class MovementIntent : Intent
156  {
157      public bool TowardOpponent { get; set; }
158  
159      protected MovementIntent(Guid operatorId, IntentType type, bool towardOpponent) 
160          : base(operatorId, type)
161      {
162          TowardOpponent = towardOpponent;
163      }
164  }
165  
166  /// <summary>
167  /// Intent to walk.
168  /// </summary>
169  public class WalkIntent : MovementIntent
170  {
171      public WalkIntent(Guid operatorId, bool towardOpponent) 
172          : base(operatorId, towardOpponent ? IntentType.WalkToward : IntentType.WalkAway, towardOpponent)
173      {
174      }
175  
176      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
177      {
178          return (true, null); // Walking is always valid
179      }
180  }
181  
182  /// <summary>
183  /// Intent to sprint.
184  /// </summary>
185  public class SprintIntent : MovementIntent
186  {
187      public SprintIntent(Guid operatorId, bool towardOpponent) 
188          : base(operatorId, towardOpponent ? IntentType.SprintToward : IntentType.SprintAway, towardOpponent)
189      {
190      }
191  
192      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
193      {
194          if (op.Stamina <= 0)
195              return (false, "Cannot sprint: no stamina");
196          
197          // Sprinting auto-exits ADS
198          
199          return (true, null);
200      }
201  }
202  
203  /// <summary>
204  /// Intent to slide.
205  /// </summary>
206  public class SlideIntent : MovementIntent
207  {
208      public SlideIntent(Guid operatorId, bool towardOpponent) 
209          : base(operatorId, towardOpponent ? IntentType.SlideToward : IntentType.SlideAway, towardOpponent)
210      {
211      }
212  
213      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
214      {
215          if (op.Stamina < op.SlideStaminaCost)
216              return (false, $"Cannot slide: need {op.SlideStaminaCost} stamina");
217          
218          if (op.MovementState == Operators.MovementState.Sliding)
219              return (false, "Already sliding");
220          
221          return (true, null);
222      }
223  }
224  
225  /// <summary>
226  /// Intent to stop all actions.
227  /// </summary>
228  public class StopIntent : Intent
229  {
230      public StopIntent(Guid operatorId) : base(operatorId, IntentType.Stop)
231      {
232      }
233  
234      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
235      {
236          return (true, null); // Always valid
237      }
238  }
239  
240  /// <summary>
241  /// Intent to start walking (state-based movement).
242  /// </summary>
243  public class WalkStateIntent : Intent
244  {
245      public long DurationMs { get; set; }
246  
247      public WalkStateIntent(Guid operatorId, long durationMs = 1000) : base(operatorId, IntentType.Walk)
248      {
249          DurationMs = durationMs;
250      }
251  
252      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
253      {
254          return (true, null); // Walking is always valid
255      }
256  }
257  
258  /// <summary>
259  /// Intent to start sprinting (state-based movement).
260  /// </summary>
261  public class SprintStateIntent : Intent
262  {
263      public long DurationMs { get; set; }
264  
265      public SprintStateIntent(Guid operatorId, long durationMs = 2000) : base(operatorId, IntentType.Sprint)
266      {
267          DurationMs = durationMs;
268      }
269  
270      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
271      {
272          if (op.Stamina <= 0)
273              return (false, "Cannot sprint: no stamina");
274          
275          return (true, null);
276      }
277  }
278  
279  /// <summary>
280  /// Intent to crouch (state-based movement).
281  /// </summary>
282  public class CrouchIntent : Intent
283  {
284      public long DurationMs { get; set; }
285  
286      public CrouchIntent(Guid operatorId, long durationMs = 5000) : base(operatorId, IntentType.Crouch)
287      {
288          DurationMs = durationMs;
289      }
290  
291      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
292      {
293          return (true, null); // Crouching is always valid
294      }
295  }
296  
297  /// <summary>
298  /// Intent to enter cover.
299  /// </summary>
300  public class EnterCoverIntent : Intent
301  {
302      public Operators.CoverState CoverType { get; set; }
303  
304      public EnterCoverIntent(Guid operatorId, Operators.CoverState coverType) : base(operatorId, IntentType.EnterCover)
305      {
306          CoverType = coverType;
307      }
308  
309      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
310      {
311          if (!Combat.MovementModel.CanEnterCover(op.CurrentMovement))
312              return (false, "Cannot enter cover while moving (must be stationary or crouching)");
313          
314          if (op.CurrentCover != Operators.CoverState.None)
315              return (false, "Already in cover");
316          
317          return (true, null);
318      }
319  }
320  
321  /// <summary>
322  /// Intent to exit cover.
323  /// </summary>
324  public class ExitCoverIntent : Intent
325  {
326      public ExitCoverIntent(Guid operatorId) : base(operatorId, IntentType.ExitCover)
327      {
328      }
329  
330      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
331      {
332          if (op.CurrentCover == Operators.CoverState.None)
333              return (false, "Not in cover");
334          
335          return (true, null);
336      }
337  }
338  
339  /// <summary>
340  /// Intent to cancel current movement.
341  /// </summary>
342  public class CancelMovementIntent : Intent
343  {
344      public CancelMovementIntent(Guid operatorId) : base(operatorId, IntentType.CancelMovement)
345      {
346      }
347  
348      public override (bool isValid, string? errorMessage) Validate(Operators.Operator op)
349      {
350          if (!op.IsMoving)
351              return (false, "Not currently moving");
352          
353          return (true, null);
354      }
355  }