diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs
index 2dd66766c..e85b54ed6 100644
--- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs
+++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs
@@ -435,12 +435,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.WriteByte(1); // Value
+ /// writer.WriteUInt(17); // EditIndex
/// });
///
///
diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs
index 67f5d7418..56e7e7f24 100644
--- a/EXILED/Exiled.API/Features/Camera.cs
+++ b/EXILED/Exiled.API/Features/Camera.cs
@@ -28,7 +28,7 @@ public class Camera : IWrapper, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary Camera079ToCamera = new(250);
+ internal static readonly Dictionary Camera079ToCamera = new(250, new ComponentsEqualityComparer());
private static readonly Dictionary NameToCameraType = new()
{
diff --git a/EXILED/Exiled.API/Features/Doors/AirlockController.cs b/EXILED/Exiled.API/Features/Doors/AirlockController.cs
index 62645a392..eceda5f31 100644
--- a/EXILED/Exiled.API/Features/Doors/AirlockController.cs
+++ b/EXILED/Exiled.API/Features/Doors/AirlockController.cs
@@ -20,7 +20,7 @@ public class AirlockController
///
/// A containing all known 's and their corresponding .
///
- internal static readonly Dictionary BaseToExiledControllers = new();
+ internal static readonly Dictionary BaseToExiledControllers = new(new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs
index 584dc3fb0..230e05b0b 100644
--- a/EXILED/Exiled.API/Features/Doors/Door.cs
+++ b/EXILED/Exiled.API/Features/Doors/Door.cs
@@ -38,7 +38,7 @@ public class Door : TypeCastObject, IWrapper, IWorldSpace
///
/// A containing all known 's and their corresponding .
///
- internal static readonly Dictionary DoorVariantToDoor = new();
+ internal static readonly Dictionary DoorVariantToDoor = new(new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Generator.cs b/EXILED/Exiled.API/Features/Generator.cs
index b3f5bfb69..315f9af0f 100644
--- a/EXILED/Exiled.API/Features/Generator.cs
+++ b/EXILED/Exiled.API/Features/Generator.cs
@@ -26,7 +26,7 @@ public class Generator : IWrapper, IWorldSpace
///
/// A of on the map.
///
- internal static readonly Dictionary Scp079GeneratorToGenerator = new();
+ internal static readonly Dictionary Scp079GeneratorToGenerator = new(new ComponentsEqualityComparer());
private Room room;
///
diff --git a/EXILED/Exiled.API/Features/Hazards/Hazard.cs b/EXILED/Exiled.API/Features/Hazards/Hazard.cs
index 6861d1a68..36695f6de 100644
--- a/EXILED/Exiled.API/Features/Hazards/Hazard.cs
+++ b/EXILED/Exiled.API/Features/Hazards/Hazard.cs
@@ -25,7 +25,7 @@ public class Hazard : TypeCastObject, IWrapper
///
/// with to it's .
///
- internal static readonly Dictionary EnvironmentalHazardToHazard = new();
+ internal static readonly Dictionary EnvironmentalHazardToHazard = new(new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Items/Ammo.cs b/EXILED/Exiled.API/Features/Items/Ammo.cs
index 4c88a7f84..64c598ad7 100644
--- a/EXILED/Exiled.API/Features/Items/Ammo.cs
+++ b/EXILED/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/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs
index fd0bba35a..b38f4aa18 100644
--- a/EXILED/Exiled.API/Features/Items/Item.cs
+++ b/EXILED/Exiled.API/Features/Items/Item.cs
@@ -43,7 +43,7 @@ public class Item : TypeCastObject- , IWrapper
///
/// A dictionary of all 's that have been converted into .
///
- internal static readonly Dictionary BaseToItem = new();
+ internal static readonly Dictionary BaseToItem = new(new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs
index 944e5bd77..5222ca985 100644
--- a/EXILED/Exiled.API/Features/Lift.cs
+++ b/EXILED/Exiled.API/Features/Lift.cs
@@ -33,7 +33,7 @@ public class Lift : IWrapper, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary ElevatorChamberToLift = new(8);
+ internal static readonly Dictionary ElevatorChamberToLift = new(8, new ComponentsEqualityComparer());
///
/// Internal list that contains all ElevatorDoor for current group.
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index b62524745..9172aab3e 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -80,6 +80,16 @@ public class Player : TypeCastObject, IEntity, IWorldSpace
/// 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 readonly HashSet componentsInChildren = new();
@@ -2382,21 +2392,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/Exiled.API/Features/Ragdoll.cs b/EXILED/Exiled.API/Features/Ragdoll.cs
index 9d73104fe..e1aefa56a 100644
--- a/EXILED/Exiled.API/Features/Ragdoll.cs
+++ b/EXILED/Exiled.API/Features/Ragdoll.cs
@@ -40,7 +40,7 @@ public class Ragdoll : IWrapper, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary BasicRagdollToRagdoll = new(250);
+ internal static readonly Dictionary BasicRagdollToRagdoll = new(250, new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Recontainer.cs b/EXILED/Exiled.API/Features/Recontainer.cs
index bdbbfcdf7..a63f8ada5 100644
--- a/EXILED/Exiled.API/Features/Recontainer.cs
+++ b/EXILED/Exiled.API/Features/Recontainer.cs
@@ -35,6 +35,11 @@ public static class Recontainer
///
public static bool IsCassieBusy => Base.CassieBusy;
+ ///
+ /// Gets a value about how many generator have been activated.
+ ///
+ public static int EngagedGeneratorCount => Base._prevEngaged;
+
///
/// Gets or sets a value indicating whether the containment zone is open.
///
diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs
index 27abeded8..dcf4e27cb 100644
--- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs
+++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs
@@ -8,9 +8,11 @@
namespace Exiled.API.Features.Roles
{
using System.Collections.Generic;
+ using System.Reflection;
using Exiled.API.Features.Pools;
+ using HarmonyLib;
using PlayerRoles;
using PlayerRoles.FirstPersonControl;
@@ -24,6 +26,7 @@ namespace Exiled.API.Features.Roles
///
public abstract class FpcRole : Role
{
+ private static FieldInfo enableFallDamageField;
private bool isUsingStamina = true;
///
@@ -55,6 +58,19 @@ public RelativePosition RelativePosition
set => FirstPersonController.FpcModule.Motor.ReceivedPosition = value;
}
+ ///
+ /// Gets or sets a value indicating whether if the player should get damage.
+ ///
+ public bool IsFallDamageEnable
+ {
+ get => FirstPersonController.FpcModule.Motor._enableFallDamage;
+ set
+ {
+ enableFallDamageField ??= AccessTools.Field(typeof(FpcMotor), nameof(FpcMotor._enableFallDamage));
+ enableFallDamageField.SetValue(FirstPersonController.FpcModule.Motor, value);
+ }
+ }
+
///
/// Gets or sets a value indicating whether if a rotation is detected on the player.
///
diff --git a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs
index aecc872d4..eff90bc44 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs
@@ -25,7 +25,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-049.
///
- public class Scp049Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp049Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs
index 8782137a7..a3de5059c 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs
@@ -16,6 +16,7 @@ namespace Exiled.API.Features.Roles
using MapGeneration;
using Mirror;
using PlayerRoles;
+ using PlayerRoles.PlayableScps;
using PlayerRoles.PlayableScps.Scp079;
using PlayerRoles.PlayableScps.Scp079.Cameras;
using PlayerRoles.PlayableScps.Scp079.Pinging;
@@ -31,7 +32,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-079.
///
- public class Scp079Role : Role, ISubroutinedScpRole
+ public class Scp079Role : Role, ISubroutinedScpRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs
index 6d2662ad3..f30342b17 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs
@@ -11,6 +11,7 @@ namespace Exiled.API.Features.Roles
using System.Linq;
using PlayerRoles;
+ using PlayerRoles.PlayableScps;
using PlayerRoles.PlayableScps.HumeShield;
using PlayerRoles.PlayableScps.Scp096;
using PlayerRoles.Subroutines;
@@ -20,7 +21,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-096.
///
- public class Scp096Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp096Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp106Role.cs b/EXILED/Exiled.API/Features/Roles/Scp106Role.cs
index 826e756a5..9480a37bc 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp106Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp106Role.cs
@@ -11,6 +11,7 @@ namespace Exiled.API.Features.Roles
using Exiled.API.Enums;
using PlayerRoles;
+ using PlayerRoles.PlayableScps;
using PlayerRoles.PlayableScps.HumeShield;
using PlayerRoles.PlayableScps.Scp049;
using PlayerRoles.PlayableScps.Scp106;
@@ -24,7 +25,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-106.
///
- public class Scp106Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp106Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp173Role.cs b/EXILED/Exiled.API/Features/Roles/Scp173Role.cs
index 23536f696..3a5f625ca 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp173Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp173Role.cs
@@ -13,6 +13,7 @@ namespace Exiled.API.Features.Roles
using Exiled.API.Features.Hazards;
using Mirror;
using PlayerRoles;
+ using PlayerRoles.PlayableScps;
using PlayerRoles.PlayableScps.HumeShield;
using PlayerRoles.PlayableScps.Scp173;
using PlayerRoles.Subroutines;
@@ -23,7 +24,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-173.
///
- public class Scp173Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp173Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs
index 3f7f6fae1..0d23e48ba 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs
@@ -23,7 +23,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-3114.
///
- public class Scp3114Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp3114Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Roles/Scp939Role.cs b/EXILED/Exiled.API/Features/Roles/Scp939Role.cs
index b6dca72b8..ec404f349 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp939Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp939Role.cs
@@ -13,6 +13,7 @@ namespace Exiled.API.Features.Roles
using Exiled.API.Features.Pools;
using PlayerRoles;
+ using PlayerRoles.PlayableScps;
using PlayerRoles.PlayableScps.HumeShield;
using PlayerRoles.PlayableScps.Scp939;
using PlayerRoles.PlayableScps.Scp939.Mimicry;
@@ -28,7 +29,7 @@ namespace Exiled.API.Features.Roles
///
/// Defines a role that represents SCP-939.
///
- public class Scp939Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole
+ public class Scp939Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp
{
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs
index 8d0b70fe3..4ca8bc4d3 100644
--- a/EXILED/Exiled.API/Features/Room.cs
+++ b/EXILED/Exiled.API/Features/Room.cs
@@ -32,7 +32,7 @@ public class Room : MonoBehaviour, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary RoomIdentifierToRoom = new(250);
+ internal static readonly Dictionary RoomIdentifierToRoom = new(250, new ComponentsEqualityComparer());
///
/// Gets a of which contains all the instances.
diff --git a/EXILED/Exiled.API/Features/TeslaGate.cs b/EXILED/Exiled.API/Features/TeslaGate.cs
index 7ebdf6480..d161e6b08 100644
--- a/EXILED/Exiled.API/Features/TeslaGate.cs
+++ b/EXILED/Exiled.API/Features/TeslaGate.cs
@@ -27,7 +27,7 @@ public class TeslaGate : IWrapper, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary BaseTeslaGateToTeslaGate = new(10);
+ internal static readonly Dictionary BaseTeslaGateToTeslaGate = new(10, new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs
index 961fbab88..63ca362fd 100644
--- a/EXILED/Exiled.API/Features/Window.cs
+++ b/EXILED/Exiled.API/Features/Window.cs
@@ -25,7 +25,7 @@ public class Window : IWrapper, IWorldSpace
///
/// A containing all known s and their corresponding .
///
- internal static readonly Dictionary BreakableWindowToWindow = new();
+ internal static readonly Dictionary BreakableWindowToWindow = new(new ComponentsEqualityComparer());
///
/// Initializes a new instance of the class.
diff --git a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs
index ff3cd5762..28fd021f0 100644
--- a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs
@@ -67,7 +67,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable instruction.IsLdarg(0));
newInstructions[lastIndex].labels.Add(doorSpawn);
+ // Replace
+ // "base.RegisterUnspawnedObject(doorNametagExtension.TargetDoor, itemPickupBase.gameObject);"
+ // with "base.RegisterUnspawnedObject(ev.Door.Base, itemPickupBase.gameObject);"
offset = -1;
index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldfld) + offset;
@@ -122,7 +120,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions);
- int e = 0;
+ LocalBuilder fpcRole = generator.DeclareLocal(typeof(FpcStandardRoleBase));
+
+ // replace HumanRole to FpcStandardRoleBase
+ newInstructions.Find(x => x.opcode == OpCodes.Isinst).operand = typeof(FpcStandardRoleBase);
+
+ // after this index all invalid exit are considered Custom
+ int customExit = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0);
for (int i = 0; i < newInstructions.Count; i++)
{
- CodeInstruction codeInstruction = newInstructions[i];
- if (codeInstruction.opcode == OpCodes.Ldc_I4_0)
- {
- e++;
- if (e > 3)
- {
- newInstructions[i].opcode = OpCodes.Ldc_I4_5;
- }
- }
+ OpCode opcode = newInstructions[i].opcode;
+ if (opcode == OpCodes.Stloc_0)
+ newInstructions[i] = new(OpCodes.Stloc_S, fpcRole.LocalIndex);
+ else if (opcode == OpCodes.Ldloc_0)
+ newInstructions[i] = new(OpCodes.Ldloc_S, fpcRole.LocalIndex);
+ else if (opcode == OpCodes.Ldc_I4_0 && i > customExit)
+ newInstructions[i].opcode = OpCodes.Ldc_I4_5;
}
for (int z = 0; z < newInstructions.Count; z++)
diff --git a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
index bba54c92e..5d22e96c5 100644
--- a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
@@ -40,8 +40,8 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Newarr) + offset;
+ int offset = 2;
+ int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldarg_S && instruction.operand == (object)4) + offset;
Label ret = generator.DefineLabel();
diff --git a/EXILED/Exiled.Events/Patches/Generic/GetCustomAmmoLimit.cs b/EXILED/Exiled.Events/Patches/Generic/GetCustomAmmoLimit.cs
new file mode 100644
index 000000000..eb533862e
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Generic/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.Generic
+{
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Generic/GetCustomCategoryLimit.cs b/EXILED/Exiled.Events/Patches/Generic/GetCustomCategoryLimit.cs
new file mode 100644
index 000000000..8ceff7538
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Generic/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.Generic
+{
+ 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);
+ }
+ }
+}