diff --git a/Defs/GameSetupSteps/SubsequentShotCurve.xml b/Defs/GameSetupSteps/SubsequentShotCurve.xml
new file mode 100644
index 0000000000..d9b6b0ebb4
--- /dev/null
+++ b/Defs/GameSetupSteps/SubsequentShotCurve.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ CE_SubsequentShotCurve
+ 1000
+
+
+
+
+ (0, 0)
+ (1.5, 0.60)
+ (1.9, 0.80)
+ (13.0, 0.95)
+ (17.0, 1.0)
+
+
+
+
+
\ No newline at end of file
diff --git a/Languages/English/Keyed/ModMenu.xml b/Languages/English/Keyed/ModMenu.xml
index fa154a3ed4..455c2fa741 100644
--- a/Languages/English/Keyed/ModMenu.xml
+++ b/Languages/English/Keyed/ModMenu.xml
@@ -108,6 +108,9 @@
Controls how many wall fragments will spawn and how big they will be.
Fragments from walls intensity
+ Controls how fast subsequent shot bonuses will accumulate.
+ Subsequent shot difficulty
+
When enabled, subsequent shots at the same, or nearby, targets are faster.
Faster subsequent shots
diff --git a/Source/CombatExtended/CombatExtended/GameSetupSteps/GameSetupStep_SubsequentShotCurve.cs b/Source/CombatExtended/CombatExtended/GameSetupSteps/GameSetupStep_SubsequentShotCurve.cs
new file mode 100644
index 0000000000..a8e0f4184e
--- /dev/null
+++ b/Source/CombatExtended/CombatExtended/GameSetupSteps/GameSetupStep_SubsequentShotCurve.cs
@@ -0,0 +1,20 @@
+using Verse;
+
+namespace CombatExtended;
+
+public class GameSetupStep_SubsequentShotCurve : GameSetupStep
+{
+ public SimpleCurve defaultSubsequentShotCurve;
+
+ public override int SeedPart => 58224852; // unused, but required
+
+ public override void GenerateFresh()
+ {
+ Verb_ShootCE.SubsequentShotCurve = defaultSubsequentShotCurve;
+ }
+
+ public override void GenerateFromScribe()
+ {
+ Verb_ShootCE.SubsequentShotCurve = defaultSubsequentShotCurve;
+ }
+}
diff --git a/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs b/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs
index 287a1660c3..999848ad2a 100755
--- a/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs
+++ b/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs
@@ -41,6 +41,7 @@ public class Settings : ModSettings, ISettingsCE
private bool midBurstRetarget = true;
private bool fasterRepeatShots = true;
+ private float fasterRepeatShotsRecoilMult = 1.0f;
private float explosionPenMultiplier = 1.0f;
private float explosionFalloffFactor = 1.0f;
@@ -121,6 +122,7 @@ public class Settings : ModSettings, ISettingsCE
private bool debugDisplayCellCoverRating = false;
private bool debugDisplayAttritionInfo = false;
private bool debugWorldShellingDamageRandomness = false;
+ private bool debugSubsequentShotLogging = false;
public bool DebuggingMode => debuggingMode;
public bool DebugVerbose => debugVerbose;
@@ -136,6 +138,7 @@ public class Settings : ModSettings, ISettingsCE
public bool DebugDisplayCellCoverRating => debugDisplayCellCoverRating && debuggingMode;
public bool DebugDisplayAttritionInfo => debugDisplayAttritionInfo && debuggingMode;
public bool DebugWorldShellingDamageRandomness => debugWorldShellingDamageRandomness && debuggingMode;
+ public bool DebugSubsequentShotLogging => debugSubsequentShotLogging && debuggingMode;
#endregion
#region Autopatcher
@@ -160,6 +163,7 @@ public class Settings : ModSettings, ISettingsCE
public float FragmentsFromWallsIntensity => fragmentsFromWallsIntensity;
public bool FasterRepeatShots => fasterRepeatShots;
+ public float FasterRepeatShotsRecoilMult => fasterRepeatShotsRecoilMult;
public bool MidBurstRetarget => midBurstRetarget;
public float ExplosionPenMultiplier => explosionPenMultiplier;
@@ -220,6 +224,7 @@ public override void ExposeData()
Scribe_Values.Look(ref debugDisplayCellCoverRating, "debugDisplayCellCoverRating", false);
Scribe_Values.Look(ref debugDisplayAttritionInfo, "debugDisplayAttritionInfo", false);
Scribe_Values.Look(ref debugWorldShellingDamageRandomness, "debugWorldShellingDamageRandomness", false);
+ Scribe_Values.Look(ref debugSubsequentShotLogging, "debugSubsequentShotLogging", false);
Scribe_Values.Look(ref debugGenClosetPawn, "debugGenClosetPawn", false);
Scribe_Values.Look(ref debugVerbose, "debugVerbose", false);
Scribe_Values.Look(ref debugMuzzleFlash, "debugMuzzleFlash", false);
@@ -256,6 +261,7 @@ public override void ExposeData()
Scribe_Values.Look(ref fragmentsFromWallsReflected, "fragmentsFromWallsReflected", false);
Scribe_Values.Look(ref fragmentsFromWallsIntensity, "fragmentsFromWallsIntensity", 1.0f);
Scribe_Values.Look(ref fasterRepeatShots, "fasterRepeatShots", false);
+ Scribe_Values.Look(ref fasterRepeatShotsRecoilMult, "fasterRepeatShotsRecoilMult", 1.0f);
Scribe_Values.Look(ref midBurstRetarget, "midBurstRetarget", true);
Scribe_Values.Look(ref explosionPenMultiplier, "explosionPenMultiplier", 1.0f);
Scribe_Values.Look(ref explosionFalloffFactor, "explosionFalloffFactor", 1.0f);
@@ -305,7 +311,6 @@ private void DoSettingsWindowContents_Mechanics(Listing_Standard list)
left.CheckboxLabeled("CE_Settings_AllowMeleeHunting_Title".Translate(), ref allowMeleeHunting, "CE_Settings_AllowMeleeHunting_Desc".Translate());
left.CheckboxLabeled("CE_Settings_SmokeEffects_Title".Translate(), ref smokeEffects, "CE_Settings_SmokeEffects_Desc".Translate());
left.CheckboxLabeled("CE_Settings_TurretsBreakShields_Title".Translate(), ref turretsBreakShields, "CE_Settings_TurretsBreakShields_Desc".Translate());
- left.CheckboxLabeled("CE_Settings_FasterRepeatShots_Title".Translate(), ref fasterRepeatShots, "CE_Settings_FasterRepeatShots_Desc".Translate());
left.CheckboxLabeled("CE_Settings_MidBurstRetarget_Title".Translate(), ref midBurstRetarget, "CE_Settings_MidBurstRetarget_Desc".Translate());
left.CheckboxLabeled("CE_Settings_EnableArcOfFire_Title".Translate(), ref enableArcOfFire, "CE_Settings_EnableArcOfFire_Desc".Translate());
left.CheckboxLabeled("CE_Settings_EnableCIWS".Translate(), ref enableCIWS, "CE_Settings_EnableCIWS_Desc".Translate());
@@ -333,6 +338,9 @@ private void DoSettingsWindowContents_Mechanics(Listing_Standard list)
right.Label("CE_Settings_StabilizationSettings".Translate());
right.Gap();
medicineSearchRadius = right.SliderLabeled("CE_Settings_MedicineSearchRadius_Title".Translate() + ": " + medicineSearchRadius.ToString("F0"), medicineSearchRadius, 1f, 100f, tooltip: "CE_Settings_MedicineSearchRadius_Desc".Translate(), labelPct: 0.6f);
+ right.GapLine();
+ left.CheckboxLabeled("CE_Settings_FasterRepeatShots_Title".Translate(), ref fasterRepeatShots, "CE_Settings_FasterRepeatShots_Desc".Translate());
+ fasterRepeatShotsRecoilMult = left.SliderLabeled("CE_Settings_FasterRepeatShotsMultiplier_Title".Translate() + ": " + fasterRepeatShotsRecoilMult.ToString("F1"), fasterRepeatShotsRecoilMult, 0.1f, 3f, tooltip: "CE_Settings_FasterRepeatShotsMultiplier_Desc".Translate(), labelPct: 0.6f);
right.End();
}
@@ -435,6 +443,7 @@ private void DoSettingsWindowContents_Misc(Listing_Standard list)
list.CheckboxLabeled("Display danger buildup within cells", ref debugDisplayDangerBuildup);
list.CheckboxLabeled("Display cover rating of cells of suppressed pawns", ref debugDisplayCellCoverRating);
list.CheckboxLabeled("Disable randomness in world shelling", ref debugWorldShellingDamageRandomness);
+ list.CheckboxLabeled("Log subsequent shot calculations", ref debugSubsequentShotLogging);
}
#endif
list.Gap();
@@ -499,6 +508,7 @@ private void ResetToDefault_Mechanics()
smokeEffects = true;
turretsBreakShields = true;
fasterRepeatShots = true;
+ fasterRepeatShotsRecoilMult = 1.0f;
midBurstRetarget = true;
enableCIWS = true;
fragmentsFromWalls = false;
@@ -567,6 +577,7 @@ private void ResetToDefault_Misc()
debugDisplayDangerBuildup = false;
debugDisplayCellCoverRating = false;
debugWorldShellingDamageRandomness = false;
+ debugSubsequentShotLogging = false;
#endif
}
#endregion
diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs
index 94cc616319..9f99f8abda 100644
--- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs
+++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs
@@ -620,7 +620,7 @@ private void GetRecoilVec(ref float rotation, ref float angle)
rotation += recoilMagnitude * Rand.Range(minX, maxX);
var trd = Rand.Range(minY, maxY);
angle += recoilMagnitude * Mathf.Deg2Rad * trd;
- lastRecoilDeg += nextRecoilMagnitude * trd;
+ lastRecoilDeg += nextRecoilMagnitude * recoil; //always store non-randomized max recoil for subsequent shot purposes
}
///
@@ -1083,7 +1083,7 @@ public override bool TryCastShot()
didRetarget = Retarget();
repeating = true;
doRetarget = true;
- storedShotReduction = null;
+
var props = VerbPropsCE;
var midBurst = numShotsFired > 0;
var suppressing = CompFireModes?.CurrentAimMode == AimMode.SuppressFire;
diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs
index c964e2486c..650476f968 100755
--- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs
+++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs
@@ -30,6 +30,8 @@ public class Verb_ShootCE : Verb_LaunchProjectileCE
public Vector3 drawPos;
+ internal static SimpleCurve SubsequentShotCurve = new SimpleCurve(); // Populated on game start from a SetupStepDef
+
#endregion
#region Properties
@@ -327,13 +329,28 @@ public override void RecalculateWarmupTicks()
var d = v - u;
var newShotRotation = (-90 + Mathf.Rad2Deg * Mathf.Atan2(d.z, d.x)) % 360;
- var delta = Mathf.Abs(newShotRotation - lastShotRotation) + lastRecoilDeg;
+ var delta = Mathf.Abs(newShotRotation - lastShotRotation);
+
+ //max possible reduction for this weapon
+ float maxReduction = CompFireModes?.CurrentAimMode == AimMode.SuppressFire ? 0.1f : _isAiming ? 0.5f : 0.25f;
+ maxReduction += RecoilAmount * RecoilAmount * 0.1f;
+
+ //current reduction after pawn stats
+ float reduction = (delta / 45f) + (SubsequentShotCurve.Evaluate(lastRecoilDeg) * Controller.settings.FasterRepeatShotsRecoilMult);
+
+ if (storedShotReduction != null)
+ {
+ reduction *= (float)storedShotReduction;
+ }
+
+ reduction = Mathf.Max(maxReduction, reduction);
+ if (Controller.settings.DebugSubsequentShotLogging)
+ {
+ Log.Message($"{caster?.LabelShort} ({EquipmentSource?.LabelShort}) subsequent shot:\nreduction {reduction}; storedShotReduction {storedShotReduction}; maxReduction {maxReduction}; lastRecoilDeg {lastRecoilDeg}; delta {delta}; delta/45 {delta / 45f}");
+ }
+
lastRecoilDeg = 0;
- var maxReduction = storedShotReduction ?? (CompFireModes?.CurrentAimMode == AimMode.SuppressFire ?
- 0.1f :
- (_isAiming ? 0.5f : 0.25f));
- var reduction = Mathf.Max(maxReduction, delta / 45f);
- storedShotReduction = reduction;
+ storedShotReduction = Mathf.Clamp01(reduction);
if (reduction < 1.0f)
{