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}"); } ///