From dc9272d6d3569751107dfd63e080b17ff9a47b08 Mon Sep 17 00:00:00 2001 From: mszabo Date: Sun, 24 Nov 2024 23:59:00 +0100 Subject: [PATCH] Only consume ammo for shots that were actually fired We currently check for and reduce ammo count before checking if a shot can actually be fired. For burst weapons with interruptibleBurst = true, this can cause extra ammo to be consumed without a corresponding shot being made. Instead, only consume ammo after firing the shot. --- .../CombatExtended/Comps/CompAmmoUser.cs | 104 +++++++++--------- .../Verbs/Verb_LaunchProjectileCE.cs | 44 +------- .../CombatExtended/Verbs/Verb_ShootCE.cs | 59 ++++++++-- .../Verbs/Verb_ShootMortarCE.cs | 12 +- 4 files changed, 108 insertions(+), 111 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs b/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs index 154a0445ba..52d2173cb5 100644 --- a/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs +++ b/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs @@ -337,36 +337,12 @@ private void AssignJobToWielder(Job job) } } - public bool Notify_ShotFired() - { - if (ammoToBeDeleted != null) - { - ammoToBeDeleted.Destroy(); - ammoToBeDeleted = null; - CompInventory.UpdateInventory(); - if (!HasAmmoOrMagazine) - { - return false; - } - } - return true; - } - - public bool Notify_PostShotFired() - { - if (!HasAmmoOrMagazine) - { - DoOutOfAmmoAction(); - return false; - } - return true; - } /// /// Reduces ammo count and updates inventory if necessary, call this whenever ammo is consumed by the gun (e.g. firing a shot, clearing a jam). /// Has an optional argument for the amount of ammo to consume per shot, which defaults to 1; this caters for special cases such as different sci-fi weapons using up different amounts of the same energy cell ammo type per shot, or a double-barrelled shotgun that fires both cartridges at the same time (projectile treated as a single, more powerful bullet) /// - public bool TryReduceAmmoCount(int ammoConsumedPerShot = 1) + public void Notify_ShotFired(int ammoConsumedPerShot = 1) { ammoConsumedPerShot = (ammoConsumedPerShot > 0) ? ammoConsumedPerShot : 1; @@ -378,48 +354,72 @@ public bool TryReduceAmmoCount(int ammoConsumedPerShot = 1) // Mag-less weapons feed directly from inventory if (!HasMagazine) { - if (UseAmmo) + if (ammoToBeDeleted != null) { - if (!TryFindAmmoInInventory(out ammoToBeDeleted)) - { - return false; - } - if (ammoToBeDeleted.def != CurrentAmmo) - { - currentAmmoInt = ammoToBeDeleted.def as AmmoDef; - } - - if (ammoToBeDeleted.stackCount > 1) - { - ammoToBeDeleted = ammoToBeDeleted.SplitOff(1); - } + ammoToBeDeleted.Destroy(); + ammoToBeDeleted = null; + CompInventory.UpdateInventory(); } - return true; } - // If magazine is empty, return false + if (curMagCountInt <= 0) { - CurMagCount = 0; - return false; + Log.Error($"{parent} tried reducing its ammo count when already empty"); } // Reduce ammo count and update inventory CurMagCount = (curMagCountInt - ammoConsumedPerShot < 0) ? 0 : curMagCountInt - ammoConsumedPerShot; + } - - /*if (curMagCountInt - ammoConsumedPerShot < 0) + public bool Notify_PostShotFired() + { + if (!HasAmmoOrMagazine) { - curMagCountInt = 0; - } else + DoOutOfAmmoAction(); + return false; + } + return true; + } + + /// + /// Check whether ammo is available for firing a shot. + /// + /// + /// For weapons without a magazine, this may update the currently selected ammo type + /// if we ran out of the currently selected ammo type but have different, compatible, types + /// available in the inventory. + /// + /// + public bool TryPrepareShot() + { + if (HasMagazine) { - curMagCountInt = curMagCountInt - ammoConsumedPerShot; - }*/ + // If magazine is empty, return false + if (curMagCountInt <= 0) + { + CurMagCount = 0; + return false; + } + return true; + } - // Original: curMagCountInt--; - if (curMagCountInt < 0) + if (UseAmmo) { - TryStartReload(); + if (!TryFindAmmoInInventory(out ammoToBeDeleted)) + { + return false; + } + if (ammoToBeDeleted.def != CurrentAmmo) + { + currentAmmoInt = ammoToBeDeleted.def as AmmoDef; + } + + if (ammoToBeDeleted.stackCount > 1) + { + ammoToBeDeleted = ammoToBeDeleted.SplitOff(1); + } } + return true; } diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs index b77b26fccd..9f1bc86eee 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs @@ -41,7 +41,7 @@ public class Verb_LaunchProjectileCE : Verb protected float distance = 10f; public CompCharges compCharges = null; - public CompAmmoUser compAmmo = null; + public CompFireModes compFireModes = null; public CompChangeableProjectile compChangeable = null; public CompApparelReloadable compReloadable = null; @@ -155,26 +155,10 @@ public float ShootingAccuracy public float SightsEfficiency => EquipmentSource?.GetStatValue(CE_StatDefOf.SightsEfficiency) ?? 1f; public virtual float SwayAmplitude => Mathf.Max(0, (4.5f - ShootingAccuracy) * (EquipmentSource?.GetStatValue(CE_StatDefOf.SwayFactor) ?? 1f)); - // Ammo variables - public virtual CompAmmoUser CompAmmo - { - get - { - if (compAmmo == null && EquipmentSource != null) - { - compAmmo = EquipmentSource.TryGetComp(); - } - return compAmmo; - } - } public virtual ThingDef Projectile { get { - if (CompAmmo != null && CompAmmo.CurrentAmmo != null) - { - return CompAmmo.CurAmmoProjectile; - } if (CompChangeable != null && CompChangeable.Loaded) { return CompChangeable.Projectile; @@ -236,8 +220,6 @@ public float RecoilAmount } } - private bool IsAttacking => ShooterPawn?.CurJobDef == JobDefOf.AttackStatic || WarmingUp; - private LightingTracker _lightingTracker = null; protected LightingTracker LightingTracker { @@ -302,14 +284,7 @@ public override bool Available() } } - // Add check for reload - if (Projectile == null || (IsAttacking && CompAmmo != null && !CompAmmo.CanBeFiredNow)) - { - CompAmmo?.TryStartReload(); - resetRetarget(); - return false; - } - return true; + return Projectile != null; } /// @@ -487,19 +462,13 @@ public virtual void ShiftTarget(ShiftVecReport report, bool calculateMechanicalO Apparel LegArmor = LegArmors.MaxByWithFallback(funcArmor); #endregion - #region get CompAmmo's Current ammo projectile - - var ProjCE = (ProjectilePropertiesCE)compAmmo?.CurAmmoProjectile?.projectile ?? null; - - #endregion - #region checks for whether the pawn can penetrate armor, which armor is stronger, etc var TargetedBodyPartArmor = TorsoArmor; bool flagTorsoArmor = ((TorsoArmor?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0.1f) >= (Helmet?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0f)); - bool flag2 = ((ProjCE?.armorPenetrationSharp ?? 0f) >= (TorsoArmor?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0.1f)); + bool flag2 = (projectilePropsCE.armorPenetrationSharp >= (TorsoArmor?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0.1f)); //Headshots do too little damage too often, so if the pawn can penetrate torso armor, they should aim at it if ((flagTorsoArmor && !flag2)) { @@ -509,7 +478,7 @@ public virtual void ShiftTarget(ShiftVecReport report, bool calculateMechanicalO bool flag3 = (TargetedBodyPartArmor?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0f) >= ((LegArmor?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0f) + 4f); //bool for whether the pawn can penetrate helmet - bool flag4 = ((ProjCE?.armorPenetrationSharp ?? 0f) >= (Helmet?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0.1f)); + bool flag4 = (projectilePropsCE.armorPenetrationSharp >= (Helmet?.GetStatValue(StatDefOf.ArmorRating_Sharp) ?? 0.1f)); //if the pawn can penetrate the helmet or torso armor there's no need to aim for legs if (flag3 && (!flag4) && (!flag2)) @@ -1132,11 +1101,6 @@ public override bool TryCastShot() numShotsFired++; if (ShooterPawn != null) { - if (CompAmmo != null && !CompAmmo.CanBeFiredNow) - { - CompAmmo?.TryStartReload(); - resetRetarget(); - } if (CompReloadable != null) { CompReloadable.UsedOnce(); diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs index fdae41ceb4..c3a1709225 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs @@ -31,6 +31,8 @@ public class Verb_ShootCE : Verb_LaunchProjectileCE public Vector3 drawPos; + private CompAmmoUser compAmmo; + #endregion #region Properties @@ -134,6 +136,17 @@ public float SpreadDegrees // Whether our shooter is currently under suppressive fire private bool IsSuppressed => ShooterPawn?.TryGetComp()?.isSuppressed ?? false; + public CompAmmoUser CompAmmo + { + get + { + compAmmo ??= EquipmentSource?.TryGetComp(); + return compAmmo; + } + } + + public override ThingDef Projectile => CompAmmo?.CurrentAmmo != null ? CompAmmo.CurAmmoProjectile : base.Projectile; + #endregion #region Methods @@ -248,6 +261,25 @@ public override void WarmupComplete() } } + public override bool Available() + { + if (!base.Available()) + { + return false; + } + + // Add check for reload + bool isAttacking = ShooterPawn?.CurJobDef == JobDefOf.AttackStatic || WarmingUp; + if (isAttacking && !(CompAmmo?.CanBeFiredNow ?? true)) + { + CompAmmo?.TryStartReload(); + resetRetarget(); + return false; + } + + return true; + } + public override void VerbTickCE() { if (_isAiming) @@ -379,13 +411,9 @@ public void ExternalCallDropCasing(int randomSeedOffset = -1) public override bool TryCastShot() { - //Reduce ammunition - if (CompAmmo != null) + if (!CompAmmo?.TryPrepareShot() ?? false) { - if (!CompAmmo.TryReduceAmmoCount(((CompAmmo.Props.ammoSet != null) ? CompAmmo.Props.ammoSet.ammoConsumedPerShot : 1) * VerbPropsCE.ammoConsumedPerShotCount)) - { - return false; - } + return false; } if (base.TryCastShot()) { @@ -409,10 +437,25 @@ protected virtual bool OnCastSuccessful() { CE_Utility.GenerateAmmoCasings(projectilePropsCE, fromPawn ? drawPos : caster.DrawPos, caster.Map, AimAngle, VerbPropsCE.recoilAmount, fromPawn: fromPawn, extension: ext); } + + if (CompAmmo == null) + { + return true; + } + + int ammoConsumedPerShot = (CompAmmo.Props.ammoSet?.ammoConsumedPerShot ?? 1) * VerbPropsCE.ammoConsumedPerShotCount; + CompAmmo.Notify_ShotFired(ammoConsumedPerShot); + + if (ShooterPawn != null && !CompAmmo.CanBeFiredNow) + { + CompAmmo.TryStartReload(); + resetRetarget(); + } + // This needs to here for weapons without magazine to ensure their last shot plays sounds - if (CompAmmo != null && !CompAmmo.HasMagazine && CompAmmo.UseAmmo) + if (!CompAmmo.HasMagazine && CompAmmo.UseAmmo) { - if (!CompAmmo.Notify_ShotFired()) + if (!CompAmmo.HasAmmoOrMagazine) { if (VerbPropsCE.muzzleFlashScale > 0.01f) { diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs index 9b86b0d4bb..709d93e1b1 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs @@ -206,10 +206,6 @@ public virtual bool TryCastGlobalShot() numShotsFired++; if (ShooterPawn != null) { - if (CompAmmo != null && !CompAmmo.CanBeFiredNow) - { - CompAmmo?.TryStartReload(); - } if (CompReloadable != null) { CompReloadable.UsedOnce(); @@ -225,13 +221,7 @@ public override bool TryCastShot() { return base.TryCastShot(); } - if (CompAmmo != null) - { - if (!CompAmmo.TryReduceAmmoCount(CompAmmo.Props.ammoSet.ammoConsumedPerShot * VerbPropsCE.ammoConsumedPerShotCount)) - { - return false; - } - } + if (this.TryCastGlobalShot()) { return this.OnCastSuccessful();