From 17ffc55debfad9daddb2218807306472dd033424 Mon Sep 17 00:00:00 2001 From: OBilo <54061981+Keshash@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:45:03 +0200 Subject: [PATCH 1/4] Subsequent shot rebalance draft --- Defs/GameSetupSteps/SubsequentShotCurve.xml | 19 ++++++++++++++++++ .../GameSetupStep_SubsequentShotCurve.cs | 20 +++++++++++++++++++ .../CombatExtended/ModSettings/Settings.cs | 8 +++++++- .../Verbs/Verb_LaunchProjectileCE.cs | 3 ++- .../CombatExtended/Verbs/Verb_ShootCE.cs | 14 ++++++++----- 5 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 Defs/GameSetupSteps/SubsequentShotCurve.xml create mode 100644 Source/CombatExtended/CombatExtended/GameSetupSteps/GameSetupStep_SubsequentShotCurve.cs diff --git a/Defs/GameSetupSteps/SubsequentShotCurve.xml b/Defs/GameSetupSteps/SubsequentShotCurve.xml new file mode 100644 index 0000000000..2eb1f3805d --- /dev/null +++ b/Defs/GameSetupSteps/SubsequentShotCurve.xml @@ -0,0 +1,19 @@ + + + + + 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/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..e74261fd95 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; @@ -160,6 +161,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; @@ -256,6 +258,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 +308,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 +335,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(); } @@ -499,6 +504,7 @@ private void ResetToDefault_Mechanics() smokeEffects = true; turretsBreakShields = true; fasterRepeatShots = true; + fasterRepeatShotsRecoilMult = 1.0f; midBurstRetarget = true; enableCIWS = true; fragmentsFromWalls = false; diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs index 94cc616319..f074fa085d 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs @@ -620,7 +620,8 @@ 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; + Log.Message($"{EquipmentSource.def.label}: {lastRecoilDeg + nextRecoilMagnitude * recoil} = {lastRecoilDeg} + {nextRecoilMagnitude} * {recoil}"); + lastRecoilDeg += nextRecoilMagnitude * recoil; //always store max recoil for subsequent shot purposes } /// diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs index c964e2486c..1f76187728 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,15 @@ 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; - lastRecoilDeg = 0; + var delta = Mathf.Abs(newShotRotation - lastShotRotation); + var maxReduction = storedShotReduction ?? (CompFireModes?.CurrentAimMode == AimMode.SuppressFire ? - 0.1f : - (_isAiming ? 0.5f : 0.25f)); - var reduction = Mathf.Max(maxReduction, delta / 45f); + 0.1f + RecoilAmount*RecoilAmount/10 : + (_isAiming ? 0.5f : 0.25f)) + RecoilAmount*RecoilAmount/10; + var reduction = Mathf.Max(maxReduction, delta / 45f + SubsequentShotCurve.Evaluate(lastRecoilDeg) * Controller.settings.FasterRepeatShotsRecoilMult); + lastRecoilDeg = 0; storedShotReduction = reduction; + Log.Message($"reduction {reduction}; maxReduction {maxReduction} lastRecoilDeg {lastRecoilDeg}; storedShotReduction {storedShotReduction};"); if (reduction < 1.0f) { From f33b34c28c40ac20de0017c437f9d686f199b8d9 Mon Sep 17 00:00:00 2001 From: OBilo <54061981+Keshash@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:55:05 +0200 Subject: [PATCH 2/4] Add translation strings --- Languages/English/Keyed/ModMenu.xml | 3 +++ Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) 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/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs index 1f76187728..ca6745b338 100755 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs @@ -336,8 +336,8 @@ public override void RecalculateWarmupTicks() (_isAiming ? 0.5f : 0.25f)) + RecoilAmount*RecoilAmount/10; var reduction = Mathf.Max(maxReduction, delta / 45f + SubsequentShotCurve.Evaluate(lastRecoilDeg) * Controller.settings.FasterRepeatShotsRecoilMult); lastRecoilDeg = 0; - storedShotReduction = reduction; Log.Message($"reduction {reduction}; maxReduction {maxReduction} lastRecoilDeg {lastRecoilDeg}; storedShotReduction {storedShotReduction};"); + storedShotReduction = reduction; if (reduction < 1.0f) { From 897ff697bc422f3a4f03531f2e97913158c6216f Mon Sep 17 00:00:00 2001 From: OBilo <54061981+Keshash@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:31:27 +0200 Subject: [PATCH 3/4] make SS mult accumulate over several bursts, clean up logging --- .../CombatExtended/ModSettings/Settings.cs | 5 ++++ .../Verbs/Verb_LaunchProjectileCE.cs | 5 ++-- .../CombatExtended/Verbs/Verb_ShootCE.cs | 25 ++++++++++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs b/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs index e74261fd95..999848ad2a 100755 --- a/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs +++ b/Source/CombatExtended/CombatExtended/ModSettings/Settings.cs @@ -122,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; @@ -137,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 @@ -222,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); @@ -440,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(); @@ -573,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 f074fa085d..9f99f8abda 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs @@ -620,8 +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; - Log.Message($"{EquipmentSource.def.label}: {lastRecoilDeg + nextRecoilMagnitude * recoil} = {lastRecoilDeg} + {nextRecoilMagnitude} * {recoil}"); - lastRecoilDeg += nextRecoilMagnitude * recoil; //always store max recoil for subsequent shot purposes + lastRecoilDeg += nextRecoilMagnitude * recoil; //always store non-randomized max recoil for subsequent shot purposes } /// @@ -1084,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 ca6745b338..4dc1b87ab5 100755 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs @@ -331,13 +331,26 @@ public override void RecalculateWarmupTicks() var newShotRotation = (-90 + Mathf.Rad2Deg * Mathf.Atan2(d.z, d.x)) % 360; var delta = Mathf.Abs(newShotRotation - lastShotRotation); - var maxReduction = storedShotReduction ?? (CompFireModes?.CurrentAimMode == AimMode.SuppressFire ? - 0.1f + RecoilAmount*RecoilAmount/10 : - (_isAiming ? 0.5f : 0.25f)) + RecoilAmount*RecoilAmount/10; - var reduction = Mathf.Max(maxReduction, delta / 45f + SubsequentShotCurve.Evaluate(lastRecoilDeg) * Controller.settings.FasterRepeatShotsRecoilMult); + //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; - Log.Message($"reduction {reduction}; maxReduction {maxReduction} lastRecoilDeg {lastRecoilDeg}; storedShotReduction {storedShotReduction};"); - storedShotReduction = reduction; + storedShotReduction = Mathf.Clamp01(reduction); if (reduction < 1.0f) { From 73f48e72963fa1e668c4426c32d691e1028402f6 Mon Sep 17 00:00:00 2001 From: OBilo <54061981+Keshash@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:48:46 +0200 Subject: [PATCH 4/4] add comments --- Defs/GameSetupSteps/SubsequentShotCurve.xml | 1 + Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Defs/GameSetupSteps/SubsequentShotCurve.xml b/Defs/GameSetupSteps/SubsequentShotCurve.xml index 2eb1f3805d..d9b6b0ebb4 100644 --- a/Defs/GameSetupSteps/SubsequentShotCurve.xml +++ b/Defs/GameSetupSteps/SubsequentShotCurve.xml @@ -6,6 +6,7 @@ 1000 +
  • (0, 0)
  • (1.5, 0.60)
  • diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs index 4dc1b87ab5..650476f968 100755 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs @@ -346,7 +346,7 @@ public override void RecalculateWarmupTicks() 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}"); + Log.Message($"{caster?.LabelShort} ({EquipmentSource?.LabelShort}) subsequent shot:\nreduction {reduction}; storedShotReduction {storedShotReduction}; maxReduction {maxReduction}; lastRecoilDeg {lastRecoilDeg}; delta {delta}; delta/45 {delta / 45f}"); } lastRecoilDeg = 0;