SuppressiveFireModel.cs
1 using GUNRPG.Core.Operators; 2 using GUNRPG.Core.Weapons; 3 4 namespace GUNRPG.Core.Combat; 5 6 /// <summary> 7 /// Models suppressive fire behavior when engaging targets in full cover. 8 /// Suppressive fire is a controlled burst that applies suppression without attempting hit resolution. 9 /// This prevents unrealistic mag-dumping into cover and maintains tactical realism. 10 /// </summary> 11 public static class SuppressiveFireModel 12 { 13 /// <summary> 14 /// Default burst size for suppressive fire (rounds). 15 /// Short controlled bursts, not full magazine dumps. 16 /// </summary> 17 public const int DefaultSuppressiveBurstSize = 3; 18 19 /// <summary> 20 /// Minimum burst size for suppressive fire. 21 /// </summary> 22 public const int MinSuppressiveBurstSize = 2; 23 24 /// <summary> 25 /// Maximum burst size for suppressive fire. 26 /// </summary> 27 public const int MaxSuppressiveBurstSize = 6; 28 29 /// <summary> 30 /// Base suppression amount applied per suppressive fire burst. 31 /// Higher than individual shot suppression due to sustained fire intent. 32 /// </summary> 33 public const float BaseSuppressiveFireSuppression = 0.25f; 34 35 /// <summary> 36 /// Multiplier for suppression when target is in full cover. 37 /// Full cover amplifies psychological pressure even if no damage is possible. 38 /// </summary> 39 public const float FullCoverSuppressionMultiplier = 0.8f; 40 41 /// <summary> 42 /// Time window (ms) to remember that a target was recently visible. 43 /// After this time, AI should not continue suppressive fire. 44 /// </summary> 45 public const long TargetLastSeenWindowMs = 3000; 46 47 /// <summary> 48 /// Cooldown after suppressive fire before next action (ms). 49 /// Represents tactical pause to assess effect. 50 /// </summary> 51 public const int PostSuppressiveCooldownMs = 200; 52 53 /// <summary> 54 /// Calculates the burst size for suppressive fire based on weapon and situation. 55 /// </summary> 56 /// <param name="weapon">The weapon being fired</param> 57 /// <param name="availableAmmo">Current ammo count</param> 58 /// <returns>Number of rounds to fire in suppressive burst</returns> 59 public static int CalculateSuppressiveBurstSize(Weapon weapon, int availableAmmo) 60 { 61 // Base burst size 62 int burstSize = DefaultSuppressiveBurstSize; 63 64 // LMGs and weapons with high suppression factors get slightly larger bursts 65 if (weapon.SuppressionFactor >= 1.3f) 66 { 67 burstSize = Math.Min(MaxSuppressiveBurstSize, burstSize + 2); 68 } 69 else if (weapon.SuppressionFactor >= 1.0f) 70 { 71 burstSize = Math.Min(MaxSuppressiveBurstSize, burstSize + 1); 72 } 73 74 // Clamp to available ammo (this is the hard constraint) 75 burstSize = Math.Min(burstSize, availableAmmo); 76 77 // Return the clamped burst size (may be less than minimum if ammo is low) 78 // Caller should check ShouldUseSuppressiveFire which validates minimum ammo 79 return burstSize; 80 } 81 82 /// <summary> 83 /// Calculates suppression severity from a suppressive fire burst. 84 /// </summary> 85 /// <param name="weapon">Weapon used for suppressive fire</param> 86 /// <param name="burstSize">Number of rounds in the burst</param> 87 /// <param name="distanceMeters">Distance to target</param> 88 /// <param name="targetMovementState">Target's movement state (optional)</param> 89 /// <returns>Total suppression severity to apply</returns> 90 public static float CalculateSuppressiveBurstSeverity( 91 Weapon weapon, 92 int burstSize, 93 float distanceMeters, 94 MovementState? targetMovementState = null) 95 { 96 // Base suppression from burst 97 float baseSuppression = BaseSuppressiveFireSuppression; 98 99 // Scale by weapon suppression factor 100 baseSuppression *= weapon.SuppressionFactor; 101 102 // Scale by burst size (diminishing returns) 103 float burstMultiplier = 1.0f + (burstSize - 1) * 0.2f; // Each additional round adds 20% 104 baseSuppression *= burstMultiplier; 105 106 // Distance factor (closer = more suppressive) 107 float distanceFactor = CalculateDistanceFactor(distanceMeters); 108 baseSuppression *= distanceFactor; 109 110 // Apply full cover multiplier (reduced effectiveness since not visible) 111 baseSuppression *= FullCoverSuppressionMultiplier; 112 113 // Apply target movement modifier 114 if (targetMovementState.HasValue) 115 { 116 float movementMultiplier = MovementModel.GetSuppressionBuildupMultiplier(targetMovementState.Value); 117 baseSuppression *= movementMultiplier; 118 } 119 120 return Math.Clamp(baseSuppression, 0f, SuppressionModel.MaxSuppressionLevel); 121 } 122 123 /// <summary> 124 /// Calculates the duration of a suppressive fire burst in milliseconds. 125 /// </summary> 126 /// <param name="weapon">Weapon being fired</param> 127 /// <param name="burstSize">Number of rounds in burst</param> 128 /// <returns>Duration in milliseconds</returns> 129 public static long CalculateBurstDurationMs(Weapon weapon, int burstSize) 130 { 131 float timeBetweenShots = weapon.GetTimeBetweenShotsMs(); 132 // Duration = time for (burstSize - 1) intervals between shots 133 return (long)((burstSize - 1) * timeBetweenShots); 134 } 135 136 /// <summary> 137 /// Determines if suppressive fire should be used given the combat situation. 138 /// </summary> 139 /// <param name="attackerAmmo">Attacker's current ammo</param> 140 /// <param name="targetCoverState">Target's cover state</param> 141 /// <param name="targetLastVisibleMs">When the target was last visible (null if never seen)</param> 142 /// <param name="currentTimeMs">Current simulation time</param> 143 /// <returns>True if suppressive fire should be used</returns> 144 public static bool ShouldUseSuppressiveFire( 145 int attackerAmmo, 146 CoverState targetCoverState, 147 long? targetLastVisibleMs, 148 long currentTimeMs) 149 { 150 // Must have ammo 151 if (attackerAmmo < MinSuppressiveBurstSize) 152 return false; 153 154 // Target must be in full cover 155 if (targetCoverState != CoverState.Full) 156 return false; 157 158 // Target must have been recently visible (we know they're there) 159 if (!targetLastVisibleMs.HasValue) 160 return false; 161 162 // Check if target was seen within the memory window 163 long timeSinceVisible = currentTimeMs - targetLastVisibleMs.Value; 164 return timeSinceVisible <= TargetLastSeenWindowMs; 165 } 166 167 /// <summary> 168 /// Distance factor for suppressive fire (closer = more effective). 169 /// Similar to SuppressionModel but with different curve for sustained fire. 170 /// </summary> 171 private static float CalculateDistanceFactor(float distanceMeters) 172 { 173 // Full suppression up to 15m, then linear falloff 174 if (distanceMeters <= 15f) 175 return 1.0f; 176 if (distanceMeters <= 40f) 177 return 1.0f - (distanceMeters - 15f) / 50f; // 0.5 at 40m 178 return 0.3f; // Minimum floor at long range 179 } 180 }