diff --git a/Exiled.API/Extensions/MirrorExtensions.cs b/Exiled.API/Extensions/MirrorExtensions.cs index e30cae4196..d0bdc27796 100644 --- a/Exiled.API/Extensions/MirrorExtensions.cs +++ b/Exiled.API/Extensions/MirrorExtensions.cs @@ -407,11 +407,11 @@ public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwne /// /// EffectOnlySCP207. /// - /// MirrorExtensions.SendCustomSync(player, player.ReferenceHub.networkIdentity, typeof(PlayerEffectsController), (writer) => { - /// writer.WriteUInt64(1ul); // DirtyObjectsBit - /// writer.WriteUInt32(1); // DirtyIndexCount + /// MirrorExtensions.SendFakeSyncObject(player, player.NetworkIdentity, typeof(PlayerEffectsController), (writer) => { + /// writer.WriteULong(1ul); // DirtyObjectsBit + /// writer.WriteUInt(1); // DirtyIndexCount /// writer.WriteByte((byte)SyncList<byte>.Operation.OP_SET); // Operations - /// writer.WriteUInt32(17); // EditIndex + /// writer.WriteUInt(17); // EditIndex /// writer.WriteByte(1); // Value /// }); /// diff --git a/Exiled.API/Features/Items/Ammo.cs b/Exiled.API/Features/Items/Ammo.cs index 4c88a7f846..64c598ad7c 100644 --- a/Exiled.API/Features/Items/Ammo.cs +++ b/Exiled.API/Features/Items/Ammo.cs @@ -20,7 +20,7 @@ public class Ammo : Item, IWrapper /// /// Gets the absolute maximum amount of ammo that may be held at one time, if ammo is forcefully given to the player (regardless of worn armor or server configuration). /// - /// For accessing the maximum amount of ammo that may be held based on worn armor and server settings, see . + /// For accessing the maximum amount of ammo that may be held based on worn armor and server settings, see . /// /// public const ushort AmmoLimit = ushort.MaxValue; diff --git a/Exiled.API/Features/Player.cs b/Exiled.API/Features/Player.cs index 5763cfff55..b3a1b052e3 100644 --- a/Exiled.API/Features/Player.cs +++ b/Exiled.API/Features/Player.cs @@ -133,6 +133,16 @@ public class Player : GameEntity /// A list of the player's items. /// internal readonly List ItemsValue = new(8); + + /// + /// A dictionary of custom item category limits. + /// + internal Dictionary CustomCategoryLimits = new(); + + /// + /// A dictionary of custom ammo limits. + /// + internal Dictionary CustomAmmoLimits = new(); #pragma warning restore SA1401 private ReferenceHub referenceHub; @@ -2643,21 +2653,170 @@ public bool DropAmmo(AmmoType ammoType, ushort amount, bool checkMinimals = fals /// /// Gets the maximum amount of ammo the player can hold, given the ammo . - /// This method factors in the armor the player is wearing, as well as server configuration. - /// For the maximum amount of ammo that can be given regardless of worn armor and server configuration, see . /// /// The of the ammo to check. - /// The maximum amount of ammo this player can carry. Guaranteed to be between 0 and . - public int GetAmmoLimit(AmmoType type) => - InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), referenceHub); + /// If the method should ignore the armor the player is wearing. + /// The maximum amount of ammo this player can carry. + public ushort GetAmmoLimit(AmmoType type, bool ignoreArmor = false) + { + if (ignoreArmor) + { + if (CustomAmmoLimits.TryGetValue(type, out ushort limit)) + return limit; + + ItemType itemType = type.GetItemType(); + return ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FirstOrDefault(x => x.AmmoType == itemType).Limit; + } + + return InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), referenceHub); + } + + /// + /// Gets the maximum amount of ammo the player can hold, given the ammo . + /// This limit will scale with the armor the player is wearing. + /// For armor ammo limits, see . + /// + /// The of the ammo to check. + /// The number that will define the new limit. + public void SetAmmoLimit(AmmoType ammoType, ushort limit) + { + CustomAmmoLimits[ammoType] = limit; + + ItemType itemType = ammoType.GetItemType(); + int index = ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FindIndex(x => x.AmmoType == itemType); + MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer => + { + writer.WriteULong(2ul); + writer.WriteUInt(1); + writer.WriteByte((byte)SyncList.Operation.OP_SET); + writer.WriteInt(index); + writer.WriteAmmoLimit(new() { Limit = limit, AmmoType = itemType, }); + }); + } + + /// + /// Reset a custom limit. + /// + /// The of the ammo to reset. + public void ResetAmmoLimit(AmmoType ammoType) + { + if (!HasCustomAmmoLimit(ammoType)) + { + Log.Error($"{nameof(Player)}.{nameof(ResetAmmoLimit)}(AmmoType): AmmoType.{ammoType} does not have a custom limit."); + return; + } + + CustomAmmoLimits.Remove(ammoType); + + ItemType itemType = ammoType.GetItemType(); + int index = ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FindIndex(x => x.AmmoType == itemType); + MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer => + { + writer.WriteULong(2ul); + writer.WriteUInt(1); + writer.WriteByte((byte)SyncList.Operation.OP_SET); + writer.WriteInt(index); + writer.WriteAmmoLimit(ServerConfigSynchronizer.Singleton.AmmoLimitsSync[index]); + }); + } + + /// + /// Check if the player has a custom limit for a specific . + /// + /// The to check. + /// If the player has a custom limit for the specific . + public bool HasCustomAmmoLimit(AmmoType ammoType) => CustomAmmoLimits.ContainsKey(ammoType); /// /// Gets the maximum amount of an the player can hold, based on the armor the player is wearing, as well as server configuration. /// /// The to check. + /// If the method should ignore the armor the player is wearing. /// The maximum amount of items in the category that the player can hold. - public int GetCategoryLimit(ItemCategory category) => - InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, referenceHub); + public sbyte GetCategoryLimit(ItemCategory category, bool ignoreArmor = false) + { + int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + + if (ignoreArmor && index != -1) + { + if (CustomCategoryLimits.TryGetValue(category, out sbyte customLimit)) + return customLimit; + + return ServerConfigSynchronizer.Singleton.CategoryLimits[index]; + } + + sbyte limit = InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, referenceHub); + + return limit == -1 ? (sbyte)1 : limit; + } + + /// + /// Set the maximum amount of an the player can hold. Only works with , , , and . + /// This limit will scale with the armor the player is wearing. + /// For armor category limits, see . + /// + /// The to check. + /// The number that will define the new limit. + public void SetCategoryLimit(ItemCategory category, sbyte limit) + { + int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + + if (index == -1) + { + Log.Error($"{nameof(Player)}.{nameof(SetCategoryLimit)}(ItemCategory, sbyte): Cannot set category limit for ItemCategory.{category}."); + return; + } + + CustomCategoryLimits[category] = limit; + + MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer => + { + writer.WriteULong(1ul); + writer.WriteUInt(1); + writer.WriteByte((byte)SyncList.Operation.OP_SET); + writer.WriteInt(index); + writer.WriteSByte(limit); + }); + } + + /// + /// Reset a custom limit. Only works with , , , and . + /// + /// The of the category to reset. + public void ResetCategoryLimit(ItemCategory category) + { + int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + + if (index == -1) + { + Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory, sbyte): Cannot reset category limit for ItemCategory.{category}."); + return; + } + + if (!HasCustomCategoryLimit(category)) + { + Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory): ItemCategory.{category} does not have a custom limit."); + return; + } + + CustomCategoryLimits.Remove(category); + + MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer => + { + writer.WriteULong(1ul); + writer.WriteUInt(1); + writer.WriteByte((byte)SyncList.Operation.OP_SET); + writer.WriteInt(index); + writer.WriteSByte(ServerConfigSynchronizer.Singleton.CategoryLimits[index]); + }); + } + + /// + /// Check if the player has a custom limit for a specific . + /// + /// The to check. + /// If the player has a custom limit for the specific . + public bool HasCustomCategoryLimit(ItemCategory category) => CustomCategoryLimits.ContainsKey(category); /// /// Adds an item of the specified type with default durability(ammo/charge) and no mods to the player's inventory. diff --git a/Exiled.Events/Patches/Fixes/GetCustomAmmoLimit.cs b/Exiled.Events/Patches/Fixes/GetCustomAmmoLimit.cs new file mode 100644 index 0000000000..1109360319 --- /dev/null +++ b/Exiled.Events/Patches/Fixes/GetCustomAmmoLimit.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System; + + using Exiled.API.Extensions; + using Exiled.API.Features; + using HarmonyLib; + using InventorySystem.Configs; + using UnityEngine; + + /// + /// Patches the delegate. + /// Sync . + /// Changes to . + /// + [HarmonyPatch(typeof(InventoryLimits), nameof(InventoryLimits.GetAmmoLimit), new Type[] { typeof(ItemType), typeof(ReferenceHub) })] + internal static class GetCustomAmmoLimit + { +#pragma warning disable SA1313 + private static void Postfix(ItemType ammoType, ReferenceHub player, ref ushort __result) + { + if (!Player.TryGet(player, out Player ply) || !ply.CustomAmmoLimits.TryGetValue(ammoType.GetAmmoType(), out ushort limit)) + return; + + __result = (ushort)Mathf.Clamp(limit + __result - InventoryLimits.GetAmmoLimit(null, ammoType), ushort.MinValue, ushort.MaxValue); + } + } +} diff --git a/Exiled.Events/Patches/Fixes/GetCustomCategoryLimit.cs b/Exiled.Events/Patches/Fixes/GetCustomCategoryLimit.cs new file mode 100644 index 0000000000..a8831cf0c1 --- /dev/null +++ b/Exiled.Events/Patches/Fixes/GetCustomCategoryLimit.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System; + + using Exiled.API.Features; + using HarmonyLib; + using InventorySystem.Configs; + using UnityEngine; + + /// + /// Patches the delegate. + /// Sync , . + /// Changes to . + /// + [HarmonyPatch(typeof(InventoryLimits), nameof(InventoryLimits.GetCategoryLimit), new Type[] { typeof(ItemCategory), typeof(ReferenceHub), })] + internal static class GetCustomCategoryLimit + { +#pragma warning disable SA1313 + private static void Postfix(ItemCategory category, ReferenceHub player, ref sbyte __result) + { + if (!Player.TryGet(player, out Player ply) || !ply.CustomCategoryLimits.TryGetValue(category, out sbyte limit)) + return; + + __result = (sbyte)Mathf.Clamp(limit + __result - InventoryLimits.GetCategoryLimit(null, category), sbyte.MinValue, sbyte.MaxValue); + } + } +}