diff --git a/EXILED/Exiled.API/Enums/RevolverChamberState.cs b/EXILED/Exiled.API/Enums/RevolverChamberState.cs
new file mode 100644
index 0000000000..421f18587d
--- /dev/null
+++ b/EXILED/Exiled.API/Enums/RevolverChamberState.cs
@@ -0,0 +1,33 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Enums
+{
+ using Exiled.API.Features.Items.FirearmModules.Primary;
+
+ ///
+ /// States for chamber in revolver cylindric magazine.
+ ///
+ ///
+ public enum RevolverChamberState
+ {
+ ///
+ /// State for empty chamber.
+ ///
+ Empty,
+
+ ///
+ /// State for chamber with a bullet.
+ ///
+ Live,
+
+ ///
+ /// State for discharged chamber.
+ ///
+ Discharged,
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs
index fb83411f2f..98cd780c24 100644
--- a/EXILED/Exiled.API/Features/Items/Firearm.cs
+++ b/EXILED/Exiled.API/Features/Items/Firearm.cs
@@ -13,6 +13,10 @@ namespace Exiled.API.Features.Items
using CameraShaking;
using Enums;
+
+ using Exiled.API.Features.Items.FirearmModules;
+ using Exiled.API.Features.Items.FirearmModules.Barrel;
+ using Exiled.API.Features.Items.FirearmModules.Primary;
using Exiled.API.Features.Pickups;
using Exiled.API.Interfaces;
using Exiled.API.Structs;
@@ -55,6 +59,20 @@ public Firearm(BaseFirearm itemBase)
: base(itemBase)
{
Base = itemBase;
+
+ foreach (ModuleBase module in Base.Modules)
+ {
+ if (module is IPrimaryAmmoContainerModule primaryAmmoModule)
+ {
+ PrimaryMagazine ??= (PrimaryMagazine)Magazine.Get(primaryAmmoModule);
+ continue;
+ }
+
+ if (module is IAmmoContainerModule ammoModule)
+ {
+ BarrelMagazine ??= (BarrelMagazine)Magazine.Get(ammoModule);
+ }
+ }
}
///
@@ -105,24 +123,94 @@ public static IReadOnlyDictionary
- /// Gets or sets the amount of ammo in the firearm.
+ /// Gets a primaty magazine for current firearm.
+ ///
+ public PrimaryMagazine PrimaryMagazine { get; }
+
+ ///
+ /// Gets a barrel magazine for current firearm.
+ ///
+ ///
+ /// for Revolver and ParticleDisruptor.
+ ///
+ public BarrelMagazine BarrelMagazine { get; }
+
+ ///
+ /// Gets or sets the amount of ammo in the firearm magazine.
+ ///
+ public int MagazineAmmo
+ {
+ get => PrimaryMagazine.Ammo;
+ set => PrimaryMagazine.Ammo = value;
+ }
+
+ ///
+ /// Gets or sets the amount of ammo in the firearm barrel.
///
- public int Ammo
+ ///
+ /// not working for Revolver and ParticleDisruptor.
+ ///
+ public int BarrelAmmo
{
- get => (Base.Modules[Array.IndexOf(Base.Modules, typeof(MagazineModule))] as MagazineModule).AmmoStored;
- set => (Base.Modules[Array.IndexOf(Base.Modules, typeof(MagazineModule))] as MagazineModule).AmmoStored = value;
+ get => BarrelMagazine?.Ammo ?? 0;
+
+ set
+ {
+ if (BarrelMagazine != null)
+ BarrelMagazine.Ammo = value;
+ }
}
+ ///
+ /// Gets the total amount of ammo in the firearm.
+ ///
+ public int TotalAmmo => Base.GetTotalStoredAmmo();
+
///
/// Gets or sets the max ammo for this firearm.
///
- /// Disruptor can't be used for MaxAmmo.
- public int MaxAmmo
+ public int MaxMagazineAmmo
+ {
+ get => PrimaryMagazine.MaxAmmo;
+ set => PrimaryMagazine.MaxAmmo = value;
+ }
+
+ ///
+ /// Gets or sets the amount of max ammo in the firearm barrel.
+ ///
+ ///
+ /// not working for Revolver and ParticleDisruptor.
+ ///
+ public int MaxBarrelAmmo
{
- get => (Base.Modules[Array.IndexOf(Base.Modules, typeof(MagazineModule))] as MagazineModule).AmmoMax;
- set => (Base.Modules[Array.IndexOf(Base.Modules, typeof(MagazineModule))] as MagazineModule)._defaultCapacity = value; // Synced?
+ get => BarrelMagazine?.MaxAmmo ?? 0;
+
+ set
+ {
+ if (BarrelMagazine != null)
+ BarrelMagazine.MaxAmmo = value;
+ }
}
+ ///
+ /// Gets the total amount of ammo in the firearm.
+ ///
+ public int TotalMaxAmmo => Base.GetTotalMaxAmmo();
+
+ ///
+ /// Gets or sets a ammo drain per shoot.
+ ///
+ ///
+ /// Always by default.
+ /// Applied on a high layer nether basegame ammo controllers.
+ ///
+ public int AmmoDrain { get; set; } = 1;
+
+ ///
+ /// Gets a value indicating whether the weapon is reloading.
+ ///
+ public bool IsReloading => Base.TryGetModule(out IReloaderModule module) && module.IsReloading;
+
///
/// Gets the of the firearm.
///
@@ -131,12 +219,12 @@ public int MaxAmmo
///
/// Gets the of the firearm.
///
- public AmmoType AmmoType => (Base.Modules.OfType().FirstOrDefault()?.AmmoType ?? ItemType.None).GetAmmoType();
+ public AmmoType AmmoType => PrimaryMagazine.AmmoType;
///
/// Gets a value indicating whether the firearm is being aimed.
///
- public bool Aiming => Base.Modules.OfType().FirstOrDefault()?.AdsTarget ?? false;
+ public bool Aiming => Base.TryGetModule(out LinearAdsModule module) && module.AdsTarget;
///
/// Gets a value indicating whether the firearm's flashlight module is enabled.
@@ -156,7 +244,7 @@ public int MaxAmmo
///
/// Gets a value indicating whether the firearm is automatic.
///
- public bool IsAutomatic => Array.Exists(Base.Modules, x => x is AutomaticActionModule);
+ public bool IsAutomatic => BarrelMagazine is AutomaticBarrelMagazine;
///
/// Gets the s of the firearm.
@@ -180,23 +268,6 @@ public IEnumerable AttachmentIdentifiers
///
public uint BaseCode => BaseCodesValue[FirearmType];
- ///
- /// Gets or sets the fire rate of the firearm, if it is an automatic weapon.
- ///
- /// This property will not do anything if the firearm is not an automatic weapon.
- ///
- public float FireRate
- {
- get => Base.Modules.OfType().FirstOrDefault()?.BaseFireRate ?? 0f;
- set
- {
- AutomaticActionModule module = Base.Modules.OfType().FirstOrDefault();
-
- if (module != null)
- module.BaseFireRate = value;
- }
- }
-
///
/// Gets or sets the recoil settings of the firearm, if it's an automatic weapon.
///
@@ -204,23 +275,14 @@ public float FireRate
///
public RecoilSettings Recoil
{
- get => Base.Modules.OfType().FirstOrDefault()?.BaseRecoil ?? default;
+ get => Base.TryGetModule(out RecoilPatternModule module) ? module.BaseRecoil : default;
set
{
- RecoilPatternModule module = Base.Modules.OfType().FirstOrDefault();
-
- if (module != null)
+ if (Base.TryGetModule(out RecoilPatternModule module))
module.BaseRecoil = value;
}
}
- /*
- ///
- /// Gets the firearm's . Will be for non-automatic weapons.
- ///
- public FirearmRecoilPattern RecoilPattern => Base is AutomaticFirearm auto ? auto._recoilPattern : null;
- */
-
///
/// Gets a of and [] which contains all available attachments for all firearms.
///
@@ -587,23 +649,15 @@ public void ClearPreferences()
///
/// Reloads current .
///
- /// Whether empty magazine should be loaded.
- public void Reload(bool emptyMagazine = false)
+ ///
+ /// For specific reloading logic you also can use for avaible weapons.
+ ///
+ public void Reload()
{
- MagazineModule magazineModule = Base.Modules.OfType().FirstOrDefault();
-
- if (magazineModule == null)
- return;
-
- magazineModule.ServerRemoveMagazine();
-
- Timing.CallDelayed(0.1f, () =>
+ if (Base.TryGetModule(out AnimatorReloaderModuleBase module))
{
- if (emptyMagazine)
- magazineModule.ServerInsertEmptyMagazine();
- else
- magazineModule.ServerInsertMagazine();
- });
+ module.StartReloading();
+ }
}
///
@@ -614,7 +668,6 @@ public override Item Clone()
{
Firearm cloneableItem = new(Type)
{
- Ammo = Ammo,
};
// TODO Not finish
@@ -639,6 +692,10 @@ internal override void ChangeOwner(Player oldOwner, Player newOwner)
{
Base.Owner = newOwner.ReferenceHub;
Base._footprintCacheSet = false;
+ foreach (ModuleBase module in Base.Modules)
+ {
+ module.OnAdded();
+ }
}
///
@@ -648,8 +705,8 @@ internal override void ReadPickupInfo(Pickup pickup)
if (pickup is FirearmPickup firearmPickup)
{
- // TODO If synced
- // MaxAmmo = firearmPickup.MaxAmmo;
+ PrimaryMagazine.MaxAmmo = firearmPickup.MaxAmmo;
+ AmmoDrain = firearmPickup.AmmoDrain;
}
}
}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/AutomaticBarrelMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/AutomaticBarrelMagazine.cs
new file mode 100644
index 0000000000..db8992d720
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/AutomaticBarrelMagazine.cs
@@ -0,0 +1,105 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Barrel
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ using UnityEngine;
+
+ ///
+ /// Basic realization of barrel.
+ ///
+ public class AutomaticBarrelMagazine : BarrelMagazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Target .
+ public AutomaticBarrelMagazine(AutomaticActionModule automaticModule)
+ : base(automaticModule)
+ {
+ AutomaticBarrel = automaticModule;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public AutomaticActionModule AutomaticBarrel { get; }
+
+ ///
+ public override Firearm Firearm => Item.Get(AutomaticBarrel.Firearm);
+
+ ///
+ public override int Ammo
+ {
+ get => AutomaticBarrel.AmmoStored;
+
+ set
+ {
+ AutomaticBarrel.AmmoStored = Mathf.Max(value, 0);
+ Resync();
+ }
+ }
+
+ ///
+ ///
+ /// Will be ranged between and due basegame logic.
+ ///
+ public override int MaxAmmo
+ {
+ get => AutomaticBarrel.ChamberSize;
+
+ set => AutomaticBarrel.ChamberSize = Mathf.Clamp(value, 0, 16);
+ }
+
+ ///
+ public override bool IsCocked
+ {
+ get => AutomaticBarrel.Cocked;
+
+ set
+ {
+ AutomaticBarrel.Cocked = value;
+ Resync();
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether barrel magazine has open bolt or not.
+ ///
+ public bool IsOpenBolted => AutomaticBarrel.OpenBolt;
+
+ ///
+ /// Gets or sets a value indicating whether barrel bolt is currently locked.
+ ///
+ public bool BoltLocked
+ {
+ get => AutomaticBarrel.BoltLocked;
+
+ set
+ {
+ AutomaticBarrel.BoltLocked = value;
+ Resync();
+ }
+ }
+
+ ///
+ /// Gets the fire rate of the firearm.
+ ///
+ public float FireRate => AutomaticBarrel.BaseFireRate;
+
+ ///
+ public override void Resync() => AutomaticBarrel.ServerResync();
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/BarrelMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/BarrelMagazine.cs
new file mode 100644
index 0000000000..b1e11f7580
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/BarrelMagazine.cs
@@ -0,0 +1,31 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Barrel
+{
+ using InventorySystem.Items.Firearms.Modules;
+
+ ///
+ /// Basic abstraction of whose are logically used to be a barrels magazines.
+ ///
+ public abstract class BarrelMagazine : Magazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// target .
+ public BarrelMagazine(IAmmoContainerModule module)
+ : base(module)
+ {
+ }
+
+ ///
+ /// Gets or sets a value indicating whether barrel is cocked.
+ ///
+ public abstract bool IsCocked { get; set; }
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/PumpBarrelMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/PumpBarrelMagazine.cs
new file mode 100644
index 0000000000..57ab59b08a
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Barrel/PumpBarrelMagazine.cs
@@ -0,0 +1,91 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Barrel
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ using UnityEngine;
+
+ ///
+ /// Basic realization of barrel.
+ ///
+ public class PumpBarrelMagazine : BarrelMagazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Target .
+ public PumpBarrelMagazine(PumpActionModule pumpModule)
+ : base(pumpModule)
+ {
+ PumpBarrel = pumpModule;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public PumpActionModule PumpBarrel { get; }
+
+ ///
+ public override Firearm Firearm => Item.Get(PumpBarrel.Firearm);
+
+ ///
+ public override int Ammo
+ {
+ get => PumpBarrel.SyncChambered;
+
+ set
+ {
+ PumpBarrel.SyncChambered = Mathf.Max(value, 0);
+ Resync();
+ }
+ }
+
+ ///
+ /// Gets or sets an amount of bullets, that pump module will try to shot.
+ ///
+ public int CockedAmmo
+ {
+ get => PumpBarrel.SyncCocked;
+
+ set
+ {
+ PumpBarrel.SyncCocked = Mathf.Max(value, 0);
+ Resync();
+ }
+ }
+
+ ///
+ public override int MaxAmmo
+ {
+ get => PumpBarrel._numberOfBarrels;
+ set => PumpBarrel._numberOfBarrels = Mathf.Max(value, 0);
+ }
+
+ ///
+ public override bool IsCocked
+ {
+ get => PumpBarrel.SyncCocked > 0;
+
+ set
+ {
+ PumpBarrel.SyncCocked = MaxAmmo;
+ Resync();
+ }
+ }
+
+ ///
+ public override void Resync() => PumpBarrel.ServerResync();
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs
new file mode 100644
index 0000000000..50a2538c64
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs
@@ -0,0 +1,111 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules
+{
+ using System;
+
+ using Exiled.API.Features.Items.FirearmModules.Barrel;
+ using Exiled.API.Features.Items.FirearmModules.Primary;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ using UnityEngine;
+
+ ///
+ /// Basic abstraction of .
+ ///
+ public abstract class Magazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// target .
+ public Magazine(IAmmoContainerModule module)
+ {
+ AmmoContainerModule = module;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public IAmmoContainerModule AmmoContainerModule { get; }
+
+ ///
+ /// Gets or sets a count of current ammo in magazine.
+ ///
+ public abstract int Ammo { get; set; }
+
+ ///
+ /// Gets or sets a max avaible ammo count in magazine.
+ ///
+ public abstract int MaxAmmo { get; set; }
+
+ ///
+ /// Gets target assotiated with this magazine.
+ ///
+ public abstract Firearm Firearm { get; }
+
+ ///
+ /// Gets wrapper to an .
+ ///
+ /// The target .
+ /// The wrapper for the given .
+ public static Magazine Get(IAmmoContainerModule module)
+ {
+ if (module == null)
+ return null;
+
+ return module switch
+ {
+ AutomaticActionModule actomatic => new AutomaticBarrelMagazine(actomatic),
+ PumpActionModule pump => new PumpBarrelMagazine(pump),
+ IPrimaryAmmoContainerModule primary => primary switch
+ {
+ MagazineModule magazine => new NormalMagazine(magazine),
+ CylinderAmmoModule cylinder => new CylinderMagazine(cylinder),
+ _ => null,
+ },
+ _ => null,
+ };
+ }
+
+ ///
+ /// Modifies stored ammo in magazine.
+ ///
+ /// Ammo change value.
+ /// Whether new ammo should be clamped in range of and .
+ /// Resultly changed ammos.
+ ///
+ /// Just a variation of the setter.
+ ///
+ public int ModifyAmmo(int delta, bool useBorders = true)
+ {
+ int oldAmmo = Ammo;
+ if (useBorders)
+ {
+ Ammo = Mathf.Clamp(Ammo + delta, 0, MaxAmmo);
+ }
+ else
+ {
+ Ammo += delta;
+ }
+
+ return Ammo - oldAmmo;
+ }
+
+ ///
+ /// Fills current to .
+ ///
+ public void Fill() => Ammo = MaxAmmo;
+
+ ///
+ /// Resyncs a related values with a client.
+ ///
+ public abstract void Resync();
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/CylinderMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/CylinderMagazine.cs
new file mode 100644
index 0000000000..2beedd7bb7
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/CylinderMagazine.cs
@@ -0,0 +1,105 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Primary
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ ///
+ /// Basic realization of .
+ ///
+ public class CylinderMagazine : PrimaryMagazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// target .
+ public CylinderMagazine(CylinderAmmoModule magazine)
+ : base(magazine)
+ {
+ CylinderModule = magazine;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public CylinderAmmoModule CylinderModule { get; }
+
+ ///
+ public override Firearm Firearm => Item.Get(CylinderModule.Firearm);
+
+ ///
+ public override int MaxAmmo
+ {
+ set
+ {
+ CylinderModule._defaultCapacity = value;
+ Resync();
+ }
+ }
+
+ ///
+ public override int ConstantMaxAmmo => CylinderModule._defaultCapacity;
+
+ ///
+ /// Gets or sets an used for this magazine.
+ ///
+ public override AmmoType AmmoType
+ {
+ get => Magazine.AmmoType.GetAmmoType();
+ set => CylinderModule.AmmoType = value.GetItemType();
+ }
+
+ ///
+ /// Gets a of chambers in cylindric magazine.
+ ///
+ public IEnumerable Chambers => CylinderAmmoModule.GetChambersArrayForSerial(CylinderModule.ItemSerial, MaxAmmo).Select(baseChamber => new Chamber(baseChamber));
+
+ ///
+ public override void Resync() => CylinderModule._needsResyncing = true;
+
+ ///
+ /// Rotates cylindric magazine by fixed rotatins.
+ ///
+ /// Rotations count.
+ public void Rotate(int rotations) => CylinderModule.RotateCylinder(rotations);
+
+ ///
+ /// A basic wrapper for chamber in cylinder magazine.
+ ///
+ public class Chamber
+ {
+ private CylinderAmmoModule.Chamber baseChamber;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Basic class.
+ internal Chamber(CylinderAmmoModule.Chamber baseChamber)
+ {
+ this.baseChamber = baseChamber;
+ }
+
+ ///
+ /// Gets or sets an state for current chamber.
+ ///
+ public RevolverChamberState State
+ {
+ get => (RevolverChamberState)baseChamber.ContextState;
+ set => baseChamber.ContextState = (CylinderAmmoModule.ChamberState)value;
+ }
+ }
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/NormalMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/NormalMagazine.cs
new file mode 100644
index 0000000000..dd8cfaa217
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/NormalMagazine.cs
@@ -0,0 +1,88 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Primary
+{
+ using System;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ ///
+ /// Basic realization of .
+ ///
+ public class NormalMagazine : PrimaryMagazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// target .
+ public NormalMagazine(MagazineModule magazine)
+ : base(magazine)
+ {
+ MagazineModule = magazine;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public MagazineModule MagazineModule { get; }
+
+ ///
+ public override Firearm Firearm => Item.Get(MagazineModule.Firearm);
+
+ ///
+ public override int MaxAmmo
+ {
+ set => MagazineModule._defaultCapacity = value;
+ }
+
+ ///
+ public override int ConstantMaxAmmo => MagazineModule._defaultCapacity;
+
+ ///
+ public override AmmoType AmmoType
+ {
+ get => Magazine.AmmoType.GetAmmoType();
+
+ set => MagazineModule._ammoType = value.GetItemType();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether magazine is inserted.
+ ///
+ public bool MagazineInserted
+ {
+ get => MagazineModule.MagazineInserted;
+
+ set
+ {
+ MagazineModule.MagazineInserted = value;
+ Resync();
+ }
+ }
+
+ ///
+ /// Removes magazine from current .
+ ///
+ ///
+ /// Affects on actual ammo count.
+ /// Removes all ammo from magazine.
+ ///
+ public void RemoveMagazine() => MagazineModule.ServerRemoveMagazine();
+
+ ///
+ /// Inserts current magazine from current .
+ ///
+ public void InsertMagazine() => MagazineModule.ServerInsertEmptyMagazine();
+
+ ///
+ public override void Resync() => MagazineModule.ServerResyncData();
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/PrimaryMagazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/PrimaryMagazine.cs
new file mode 100644
index 0000000000..39ff428556
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/PrimaryMagazine.cs
@@ -0,0 +1,63 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Items.FirearmModules.Primary
+{
+ using System;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ ///
+ /// Basic abstraction of whose are logically used to be a primary magazines.
+ ///
+ public abstract class PrimaryMagazine : Magazine
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// target .
+ public PrimaryMagazine(IPrimaryAmmoContainerModule magazine)
+ : base(magazine)
+ {
+ Magazine = magazine;
+ }
+
+ ///
+ /// Gets an original .
+ ///
+ public IPrimaryAmmoContainerModule Magazine { get; }
+
+ ///
+ public override int MaxAmmo => Magazine.AmmoMax;
+
+ ///
+ /// Gets a max avaible ammo count in magazine without attachments.
+ ///
+ public abstract int ConstantMaxAmmo { get; }
+
+ ///
+ public override int Ammo
+ {
+ get => Magazine.AmmoStored;
+
+ set
+ {
+ int modifyCount = Math.Max(0, value) - Ammo;
+ Magazine.ServerModifyAmmo(modifyCount);
+ Resync();
+ }
+ }
+
+ ///
+ /// Gets or sets an used for this magazine.
+ ///
+ public abstract AmmoType AmmoType { get; set; }
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs
index 4f20250762..e5207c99f7 100644
--- a/EXILED/Exiled.API/Features/Items/Item.cs
+++ b/EXILED/Exiled.API/Features/Items/Item.cs
@@ -16,6 +16,7 @@ namespace Exiled.API.Features.Items
using InventorySystem;
using InventorySystem.Items;
using InventorySystem.Items.Armor;
+ using InventorySystem.Items.Autosync;
using InventorySystem.Items.Firearms.Ammo;
using InventorySystem.Items.Jailbird;
using InventorySystem.Items.Keycards;
@@ -50,6 +51,10 @@ public class Item : TypeCastObject- , IWrapper
public Item(ItemBase itemBase)
{
Base = itemBase;
+
+ if (Base is ModularAutosyncItem modularItem && modularItem.InstantiationStatus is AutosyncInstantiationStatus.Template or AutosyncInstantiationStatus.SimulatedInstance)
+ return;
+
BaseToItem.Add(itemBase, this);
if (Base.ItemSerial is 0 && itemBase.Owner != null)
diff --git a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs
index 936ac1d2c3..3db97dac89 100644
--- a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs
+++ b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs
@@ -9,7 +9,12 @@ namespace Exiled.API.Features.Pickups
{
using Exiled.API.Interfaces;
+ using InventorySystem.Items;
using InventorySystem.Items.Firearms;
+ using InventorySystem.Items.Firearms.Attachments;
+ using InventorySystem.Items.Firearms.Modules;
+
+ using UnityEngine;
using BaseFirearm = InventorySystem.Items.Firearms.FirearmPickup;
@@ -36,7 +41,6 @@ internal FirearmPickup(ItemType type)
: base(type)
{
Base = (BaseFirearm)((Pickup)this).Base;
- IsDistributed = true;
// TODO not finish
/*
@@ -50,45 +54,63 @@ internal FirearmPickup(ItemType type)
public new BaseFirearm Base { get; }
///
- /// Gets or sets a value indicating whether the pickup is already distributed.
+ /// Gets a value indicating whether the pickup is already distributed.
///
- public bool IsDistributed { get; set; }
-
- // TODO NOT FINISH
- /*{
- get => Base.Distributed;
- set => Base.Distributed = value;
- }*/
+ public bool IsDistributed { get; internal set; }
- // TODO not finish
-
- /*
///
- /// Gets or sets the .
+ /// Gets or sets a value indicating how much ammo can contain this .
///
- public FirearmStatus Status
- {
- get => Base.NetworkStatus;
- set => Base.NetworkStatus = value;
- }
- */
+ public int MaxAmmo { get; set; }
///
- /// Gets or sets a value indicating how many ammo have this .
+ /// Gets or sets a value indicating how much ammo have this .
///
- /// This will be updated only when item will be picked up.
- public int Ammo { get; set; }
+ public int Ammo
+ {
+ get
+ {
+ if (!AttachmentPreview.TryGetOrAddInstance(Type, out Firearm baseFirearm))
+ return 0;
+
+ Items.Firearm firearm = Items.Item.Get(baseFirearm);
+
+ ushort oldSerial = firearm.Serial;
+
+ firearm.Serial = Serial;
+
+ int ammo = firearm.PrimaryMagazine.Ammo;
+
+ firearm.Serial = oldSerial;
+
+ return ammo;
+ }
+
+ set
+ {
+ if (!AttachmentPreview.TryGetOrAddInstance(Type, out Firearm baseFirearm))
+ return;
+
+ Items.Firearm firearm = Items.Item.Get(baseFirearm);
+
+ ushort oldSerial = firearm.Serial;
+
+ firearm.Serial = Serial;
+
+ firearm.PrimaryMagazine.Ammo = value;
+
+ firearm.Serial = oldSerial;
+ }
+ }
- /*
///
- /// Gets or sets the .
+ /// Gets or sets a ammo drain per shoot.
///
- public FirearmStatusFlags Flags
- {
- get => Base.NetworkStatus.Flags;
- set => Base.NetworkStatus = new(Base.NetworkStatus.Ammo, value, Base.NetworkStatus.Attachments);
- }
- */
+ ///
+ /// Always by default.
+ /// Applied on a high layer nether basegame ammo controllers.
+ ///
+ public int AmmoDrain { get; set; } = 1;
///
/// Gets or sets a value indicating whether the attachment code have this .
@@ -96,7 +118,15 @@ public FirearmStatusFlags Flags
public uint Attachments
{
get => Base.Worldmodel.AttachmentCode;
- set => Base.Worldmodel.AttachmentCode = value;
+ set => Base.Worldmodel.Setup(Base.CurId, Base.Worldmodel.WorldmodelType, value);
+ }
+
+ ///
+ public override void Spawn()
+ {
+ base.Spawn();
+ if (!IsDistributed)
+ Base.OnDistributed();
}
///
@@ -104,5 +134,27 @@ public uint Attachments
///
/// A string containing FirearmPickup related data.
public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{IsDistributed}| -{/*Ammo*/0}-";
+
+ ///
+ internal override void ReadItemInfo(Items.Item item)
+ {
+ Items.Firearm firearm = (Items.Firearm)item;
+ MaxAmmo = firearm.PrimaryMagazine.ConstantMaxAmmo;
+ AmmoDrain = firearm.AmmoDrain;
+ base.ReadItemInfo(item);
+ }
+
+ ///
+ protected override void InitializeProperties(ItemBase itemBase)
+ {
+ base.InitializeProperties(itemBase);
+ if (!(itemBase as Firearm).TryGetModule(out IPrimaryAmmoContainerModule magazine))
+ {
+ Log.Error($"firearm prefab {itemBase.ItemTypeId} doesnt have an primary magazine module(unexpected)");
+ return;
+ }
+
+ MaxAmmo = magazine.AmmoMax;
+ }
}
}
diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs
index 047958be7a..e4ed1b5850 100644
--- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs
+++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs
@@ -567,7 +567,7 @@ public float PickupTimeForPlayer(Player player)
/// Spawns pickup on a server.
///
///
- public void Spawn()
+ public virtual void Spawn()
{
// condition for projectiles
if (!GameObject.activeSelf)
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index b76a887530..eea891c648 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -610,18 +610,15 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences
/// Players can be cuffed without another player being the cuffer.
public bool IsCuffed => Inventory.IsDisarmed();
- // TODO NOT FINISH
- /*
///
/// Gets a value indicating whether the player is reloading a weapon.
///
- public bool IsReloading => CurrentItem is Firearm firearm && !firearm.Base.AmmoManagerModule.Standby;
+ public bool IsReloading => CurrentItem is Firearm firearm && !firearm.IsReloading;
///
/// Gets a value indicating whether the player is aiming with a weapon.
///
public bool IsAimingDownWeapon => CurrentItem is Firearm firearm && firearm.Aiming;
- */
///
/// Gets a value indicating whether the player has enabled weapon's flashlight module.
diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs
index 2789f2fd1c..5eea6969be 100644
--- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs
+++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs
@@ -698,7 +698,7 @@ public virtual void Give(Player player, Item item, bool displayMessage = true)
{
try
{
- Log.Debug($"{Name}.{nameof(Give)}: Item Serial: {item.Serial} Ammo: {(item is Firearm firearm ? firearm.Ammo : -1)}");
+ Log.Debug($"{Name}.{nameof(Give)}: Item Serial: {item.Serial} Ammo: {(item is Firearm firearm ? firearm.MagazineAmmo : -1)}");
player.AddItem(item);
diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs
index 728a81d31e..dea5aec5c6 100644
--- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs
+++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs
@@ -76,8 +76,8 @@ public override ItemType Type
if (!Attachments.IsEmpty())
firearm.AddAttachment(Attachments);
- firearm.Ammo = ClipSize;
- firearm.MaxAmmo = ClipSize;
+ firearm.MagazineAmmo = ClipSize;
+ firearm.MaxMagazineAmmo = ClipSize;
Pickup? pickup = firearm.CreatePickup(position);
@@ -104,8 +104,8 @@ public override ItemType Type
if (!Attachments.IsEmpty())
firearm.AddAttachment(Attachments);
- int ammo = firearm.Ammo;
- firearm.MaxAmmo = ClipSize;
+ int ammo = firearm.MagazineAmmo;
+ firearm.MaxMagazineAmmo = ClipSize;
Log.Debug($"{nameof(Name)}.{nameof(Spawn)}: Spawning weapon with {ammo} ammo.");
Pickup? pickup = firearm.CreatePickup(position);
pickup.Scale = Scale;
@@ -130,8 +130,8 @@ public override void Give(Player player, bool displayMessage = true)
if (!Attachments.IsEmpty())
firearm.AddAttachment(Attachments);
- firearm.Ammo = ClipSize;
- firearm.MaxAmmo = ClipSize;
+ firearm.MagazineAmmo = ClipSize;
+ firearm.MaxMagazineAmmo = ClipSize;
}
Log.Debug($"{nameof(Give)}: Adding {item.Serial} to tracker.");
@@ -214,7 +214,7 @@ private void OnInternalReloading(ReloadingWeaponEventArgs ev)
Log.Debug($"{nameof(Name)}.{nameof(OnInternalReloading)}: Continuing with internal reload..");
ev.IsAllowed = false;
- int remainingClip = ((Firearm)ev.Player.CurrentItem).Ammo;
+ int remainingClip = ((Firearm)ev.Player.CurrentItem).MagazineAmmo;
if (remainingClip >= ClipSize)
return;
@@ -229,7 +229,7 @@ private void OnInternalReloading(ReloadingWeaponEventArgs ev)
return;
}
- ev.Firearm.Reload(true);
+ ev.Firearm.Reload();
byte amountToReload = (byte)Math.Min(ClipSize - remainingClip, ev.Player.Ammo[ammoType.GetItemType()]);
@@ -241,9 +241,9 @@ private void OnInternalReloading(ReloadingWeaponEventArgs ev)
ev.Player.Ammo[ammoType.GetItemType()] -= amountToReload;
ev.Player.Inventory.SendAmmoNextFrame = true;
- ((Firearm)ev.Player.CurrentItem).Ammo = (byte)(((Firearm)ev.Player.CurrentItem).Ammo + amountToReload);
+ ((Firearm)ev.Player.CurrentItem).MagazineAmmo = (byte)(((Firearm)ev.Player.CurrentItem).MagazineAmmo + amountToReload);
- Log.Debug($"{ev.Player.Nickname} ({ev.Player.UserId}) [{ev.Player.Role}] reloaded a {Name} ({Id}) [{Type} ({((Firearm)ev.Player.CurrentItem).Ammo}/{ClipSize})]!");
+ Log.Debug($"{ev.Player.Nickname} ({ev.Player.UserId}) [{ev.Player.Role}] reloaded a {Name} ({Id}) [{Type} ({((Firearm)ev.Player.CurrentItem).MagazineAmmo}/{ClipSize})]!");
}
private void OnInternalShooting(ShootingEventArgs ev)
diff --git a/EXILED/Exiled.Events/EventArgs/Player/RotatingRevolverEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RotatingRevolverEventArgs.cs
new file mode 100644
index 0000000000..515d8c7b04
--- /dev/null
+++ b/EXILED/Exiled.Events/EventArgs/Player/RotatingRevolverEventArgs.cs
@@ -0,0 +1,64 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.EventArgs.Player
+{
+ using API.Features;
+ using API.Features.Items;
+
+ using Exiled.API.Features.Items.FirearmModules.Primary;
+
+ using Interfaces;
+
+ using InventorySystem.Items.Firearms.Modules;
+
+ using FirearmBase = InventorySystem.Items.Firearms.Firearm;
+
+ ///
+ /// Contains all information when a player rotates revolver.
+ ///
+ public class RotatingRevolverEventArgs : IPlayerEvent, IFirearmEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public RotatingRevolverEventArgs(FirearmBase firearm, int rotations)
+ {
+ Firearm = Item.Get(firearm);
+ Player = Firearm.Owner;
+ Rotations = rotations;
+ }
+
+ ///
+ /// Gets or sets a rotations count(per chamber).
+ ///
+ public int Rotations { get; set; }
+
+ ///
+ /// Gets a value indicating whether the rotation will have an effect.
+ ///
+ ///
+ /// checks rotations and chambers counts equality by mod of chambers counts.
Rotations % Chambers.Length == 0
+ ///
+ public bool HasEffect => Firearm.PrimaryMagazine.MaxAmmo % Rotations == 0;
+
+ ///
+ public Firearm Firearm { get; }
+
+ ///
+ public Item Item => Firearm;
+
+ ///
+ public Player Player { get; }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs
index 42dcf7efab..d876754505 100644
--- a/EXILED/Exiled.Events/Handlers/Player.cs
+++ b/EXILED/Exiled.Events/Handlers/Player.cs
@@ -553,6 +553,11 @@ public class Player
///
public static Event ChangedEmotion { get; set; } = new();
+ ///
+ /// Invoked before a 's rotates the revolver.
+ ///
+ public static Event RotatingRevolver { get; set; } = new();
+
///
/// Invoked before disruptor's mode is changed.
///
@@ -1193,6 +1198,12 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
/// The instance.
public static void OnChangingNickname(ChangingNicknameEventArgs ev) => ChangingNickname.InvokeSafely(ev);
+ ///
+ /// Called before a 's rotates the revolver.
+ ///
+ /// The instance.
+ public static void OnRotatingRevolver(RotatingRevolverEventArgs ev) => RotatingRevolver.InvokeSafely(ev);
+
///
/// Called before disruptor's mode is changed.
///
diff --git a/EXILED/Exiled.Events/Patches/Events/Player/RotatingRevolver.cs b/EXILED/Exiled.Events/Patches/Events/Player/RotatingRevolver.cs
new file mode 100644
index 0000000000..7dd1ea6065
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Events/Player/RotatingRevolver.cs
@@ -0,0 +1,71 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Events.Player
+{
+ using System.Collections.Generic;
+ using System.Reflection.Emit;
+
+ using Exiled.API.Extensions;
+ using Exiled.API.Features.Pools;
+
+ using Exiled.Events.Attributes;
+
+ using Exiled.Events.EventArgs.Player;
+
+ using Exiled.Events.Handlers;
+
+ using HarmonyLib;
+ using InventorySystem.Items.Firearms.Modules;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patches
+ /// to add event.
+ ///
+ [EventPatch(typeof(Player), nameof(Player.RotatingRevolver))]
+ [HarmonyPatch(typeof(CylinderAmmoModule), nameof(CylinderAmmoModule.RotateCylinder))]
+ internal class RotatingRevolver
+ {
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ int offset = -2;
+ int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Rem) + offset;
+
+ newInstructions.InsertRange(
+ index,
+ new[]
+ {
+ // this.Firearm;
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new(OpCodes.Callvirt, PropertyGetter(typeof(CylinderAmmoModule), nameof(CylinderAmmoModule.Firearm))),
+
+ // rotations
+ new(OpCodes.Ldarg_1),
+
+ // RotatingRevolverEventArgs ev = new(firearm, rotations);
+ new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RotatingRevolverEventArgs))[0]),
+ new(OpCodes.Dup),
+
+ // Handlers.Player.OnRotatingRevolver(ev);
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.OnRotatingRevolver))),
+
+ // rotations = ev.Rotations
+ new(OpCodes.Callvirt, PropertyGetter(typeof(RotatingRevolverEventArgs), nameof(RotatingRevolverEventArgs.Rotations))),
+ new(OpCodes.Starg_S, 1),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Generic/AmmoDrain.cs b/EXILED/Exiled.Events/Patches/Generic/AmmoDrain.cs
new file mode 100644
index 0000000000..cf51d62adc
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Generic/AmmoDrain.cs
@@ -0,0 +1,246 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Generic
+{
+#pragma warning disable SA1402
+#pragma warning disable SA1649
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection.Emit;
+
+ using Exiled.API.Extensions;
+ using Exiled.API.Features;
+ using Exiled.API.Features.Items;
+ using Exiled.API.Features.Pools;
+
+ using HarmonyLib;
+
+ using InventorySystem.Items;
+ using InventorySystem.Items.Firearms;
+ using InventorySystem.Items.Firearms.Modules;
+
+ using MapGeneration;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patch for adding support for automatic weapons.
+ ///
+ [HarmonyPatch(typeof(AutomaticActionModule))]
+ internal class AmmoDrainAutomatic
+ {
+ ///
+ /// Constructs instructions for modifying ammo by .
+ ///
+ /// Firearm local variable.
+ /// AmmoDrain local variable..
+ /// Label for help.
+ /// Instructions for .
+ internal static IEnumerable GetInstructions(LocalBuilder firearm, LocalBuilder ammoDrain, Label continueLabel)
+ {
+ // set ammoDrain to 1 by default
+ yield return new CodeInstruction(OpCodes.Ldc_I4_1);
+ yield return new CodeInstruction(OpCodes.Stloc_S, ammoDrain.LocalIndex);
+
+ // Firearm firearm = Item.Get((this.Firearm));
+ yield return new CodeInstruction(OpCodes.Ldarg_0);
+ yield return new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(FirearmSubcomponentBase), nameof(FirearmSubcomponentBase.Firearm)));
+ yield return new CodeInstruction(OpCodes.Call, GetDeclaredMethods(typeof(Item)).First(x => !x.IsGenericMethod && x.Name is nameof(Item.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemBase)));
+ yield return new CodeInstruction(OpCodes.Isinst, typeof(API.Features.Items.Firearm));
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Stloc_S, firearm.LocalIndex);
+ yield return new CodeInstruction(OpCodes.Brfalse_S, continueLabel);
+
+ // if (firearm is not null) {
+ // ammoDrain = firearm.AmmoDrain;
+ // }
+ yield return new CodeInstruction(OpCodes.Ldloc_S, firearm.LocalIndex);
+ yield return new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(API.Features.Items.Firearm), nameof(API.Features.Items.Firearm.AmmoDrain)));
+ yield return new CodeInstruction(OpCodes.Stloc_S, ammoDrain.LocalIndex);
+
+ yield return new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel);
+ }
+
+ // that patch for open bolted firearms
+ [HarmonyPatch(nameof(AutomaticActionModule.ServerShoot))]
+ [HarmonyTranspiler]
+ private static IEnumerable First(IEnumerable codeInstructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(codeInstructions);
+
+ LocalBuilder firearm = generator.DeclareLocal(typeof(API.Features.Items.Firearm));
+ LocalBuilder ammoDrain = generator.DeclareLocal(typeof(int));
+
+ Label cnt = generator.DefineLabel();
+
+ // insert default instructions for getting ammo drain
+ newInstructions.InsertRange(0, GetInstructions(firearm, ammoDrain, cnt));
+
+ int offset = 1;
+ int index = newInstructions.FindIndex(i => i.Calls(PropertyGetter(typeof(IAmmoContainerModule), nameof(IAmmoContainerModule.AmmoStored)))) + offset;
+
+ // divide max ammo by AmmoDrain
+ // we cant cock more ammo than we have in primary magazine, so we limit it due ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Div),
+ });
+
+ offset = 0;
+ index = newInstructions.FindIndex(i => i.Calls(Method(typeof(IPrimaryAmmoContainerModule), nameof(IPrimaryAmmoContainerModule.ServerModifyAmmo)))) + offset;
+
+ // multiply ammo that are removed from primary magazine, actuall logic of ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Mul),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+
+ // that patch for non open bolted firearms
+ [HarmonyPatch(nameof(AutomaticActionModule.ServerCycleAction))]
+ [HarmonyTranspiler]
+ private static IEnumerable Second(IEnumerable codeInstructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(codeInstructions);
+
+ LocalBuilder firearm = generator.DeclareLocal(typeof(API.Features.Items.Firearm));
+ LocalBuilder ammoDrain = generator.DeclareLocal(typeof(int));
+
+ // insert default instructions for getting ammo drain
+ Label cnt = generator.DefineLabel();
+
+ newInstructions.InsertRange(0, GetInstructions(firearm, ammoDrain, cnt));
+
+ int offset = 1;
+ int index = newInstructions.FindIndex(i => i.Calls(PropertyGetter(typeof(IAmmoContainerModule), nameof(IAmmoContainerModule.AmmoStored)))) + offset;
+
+ // divide max ammo by AmmoDrain
+ // we cant cock more ammo than we have in primary magazine, so we limit it due ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Div),
+ });
+
+ offset = 0;
+ index = newInstructions.FindIndex(i => i.Calls(Method(typeof(IPrimaryAmmoContainerModule), nameof(IPrimaryAmmoContainerModule.ServerModifyAmmo)))) + offset;
+
+ // multiply ammo that are removed from primary magazine, actuall logic of ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Mul),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+
+ ///
+ /// Patch for adding support for shotguns.
+ ///
+ [HarmonyPatch(typeof(PumpActionModule), nameof(PumpActionModule.Pump))]
+ internal class AmmoDrainPump
+ {
+ private static IEnumerable Transpiler(IEnumerable codeInstructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(codeInstructions);
+
+ LocalBuilder firearm = generator.DeclareLocal(typeof(API.Features.Items.Firearm));
+ LocalBuilder ammoDrain = generator.DeclareLocal(typeof(int));
+
+ Label cnt = generator.DefineLabel();
+
+ // insert default instructions for getting ammo drain
+ newInstructions.InsertRange(0, AmmoDrainAutomatic.GetInstructions(firearm, ammoDrain, cnt));
+
+ int offset = 1;
+ int index = newInstructions.FindLastIndex(i => i.Calls(PropertyGetter(typeof(PumpActionModule), nameof(PumpActionModule.AmmoStored)))) + offset;
+
+ // divide max ammo by AmmoDrain
+ // we cant cock more ammo than we have in primary magazine, so we limit it due ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Div),
+ });
+
+ offset = 0;
+ index = newInstructions.FindIndex(i => i.Calls(Method(typeof(IPrimaryAmmoContainerModule), nameof(IPrimaryAmmoContainerModule.ServerModifyAmmo)))) + offset;
+
+ // multiply ammo that are removed from primary magazine, actuall logic of ammo drain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Mul),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+
+ ///
+ /// Patch for adding support for revolvers.
+ ///
+ [HarmonyPatch(typeof(RevolverClipReloaderModule), nameof(RevolverClipReloaderModule.InsertAmmoFromClip))]
+ internal class AmmoDrainRevolver
+ {
+ private static IEnumerable Transpiler(IEnumerable codeInstructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(codeInstructions);
+
+ LocalBuilder firearm = generator.DeclareLocal(typeof(API.Features.Items.Firearm));
+ LocalBuilder ammoDrain = generator.DeclareLocal(typeof(int));
+
+ Label cnt = generator.DefineLabel();
+
+ // insert default instructions for getting ammo drain
+ newInstructions.InsertRange(0, AmmoDrainAutomatic.GetInstructions(firearm, ammoDrain, cnt));
+
+ int offset = 1;
+ int index = newInstructions.FindIndex(i => i.Calls(PropertyGetter(typeof(IAmmoContainerModule), nameof(IAmmoContainerModule.AmmoMax)))) + offset;
+
+ // multiply max ammo by AmmoDrain
+ // there is little different logic for revolver, for other weapons we can patch methods for removing ammo after shooting/pumping, but for revolver we only can patch method for reload
+ // so our actions are inverted, multiply max ammo limit to select more ammo due AmmoDrain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Mul),
+ });
+
+ offset = 0;
+ index = newInstructions.FindIndex(i => i.Calls(Method(typeof(IPrimaryAmmoContainerModule), nameof(IPrimaryAmmoContainerModule.ServerModifyAmmo)))) + offset;
+
+ // and divide ammo that are inserting in magazine, to implement AmmoDrain
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ new(OpCodes.Ldloc_S, ammoDrain.LocalIndex),
+ new(OpCodes.Div),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Example/Events/PlayerHandler.cs b/EXILED/Exiled.Example/Events/PlayerHandler.cs
index 9f37aecb1e..77cebae442 100644
--- a/EXILED/Exiled.Example/Events/PlayerHandler.cs
+++ b/EXILED/Exiled.Example/Events/PlayerHandler.cs
@@ -164,7 +164,7 @@ public void OnShooting(ShootingEventArgs ev)
///
public void OnReloading(ReloadingWeaponEventArgs ev)
{
- Log.Info($"{ev.Player.Nickname} is reloading their {ev.Firearm.Type}. They have {ev.Firearm.Ammo} ammo. Using ammo type {ev.Firearm.AmmoType}");
+ Log.Info($"{ev.Player.Nickname} is reloading their {ev.Firearm.Type}. They have {ev.Firearm.MagazineAmmo} ammo. Using ammo type {ev.Firearm.AmmoType}");
}
///