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 }