From 4e508261a3b3879bef6512b7b7f9afd1a039eb51 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:33:05 +0200 Subject: [PATCH 1/9] RecontainedEventArgs more feature (#20) * RecontainedEventArgs more feature * Fix Naming * grammar * Update RecontainedEventArgs.cs --------- Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> --- .../EventArgs/Scp079/RecontainedEventArgs.cs | 25 +++++++++++++++++-- .../Patches/Events/Scp079/Recontain.cs | 3 ++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Scp079/RecontainedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp079/RecontainedEventArgs.cs index 84a2e93ad..49b5dbebd 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp079/RecontainedEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp079/RecontainedEventArgs.cs @@ -22,10 +22,16 @@ public class RecontainedEventArgs : IScp079Event /// /// /// - public RecontainedEventArgs(Player player) + /// + /// + /// + public RecontainedEventArgs(Player player, PlayerRoles.PlayableScps.Scp079.Scp079Recontainer scp079Recontainer) { Player = player; Scp079 = player.Role.As(); + Recontainer = scp079Recontainer; + Attacker = Player.Get(scp079Recontainer._activatorGlass.LastAttacker); + IsAutomatic = scp079Recontainer._activatorGlass.LastAttacker.IsSet; } /// @@ -35,5 +41,20 @@ public RecontainedEventArgs(Player player) /// public Scp079Role Scp079 { get; } + + /// + /// Gets the instance that handle SCP-079 recontained proccess. + /// + public PlayerRoles.PlayableScps.Scp079.Scp079Recontainer Recontainer { get; } + + /// + /// Gets the player who recontained SCP-079. + /// + public Player Attacker { get; } + + /// + /// Gets a value indicating whether the recontainment has been made automatically or by triggering the process. + /// + public bool IsAutomatic { get; } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Patches/Events/Scp079/Recontain.cs b/EXILED/Exiled.Events/Patches/Events/Scp079/Recontain.cs index 991ffc47e..3338c84ab 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp079/Recontain.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp079/Recontain.cs @@ -37,9 +37,10 @@ private static IEnumerable Transpiler(IEnumerable Date: Tue, 6 Aug 2024 15:59:41 -0400 Subject: [PATCH 2/9] InteractingScp330.cs: Reduction of bloat code from original design. (#30) * Should reduce bloat code that was required years ago. * Should reduce bloat code that was required years ago. * Added back per Yamato's request --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Events/Scp330/InteractingScp330.cs | 87 ++----------------- 1 file changed, 7 insertions(+), 80 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Scp330/InteractingScp330.cs b/EXILED/Exiled.Events/Patches/Events/Scp330/InteractingScp330.cs index 0c43bbadd..aea840168 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp330/InteractingScp330.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp330/InteractingScp330.cs @@ -10,18 +10,12 @@ namespace Exiled.Events.Patches.Events.Scp330 using System.Collections.Generic; using System.Reflection.Emit; - using API.Features.Pools; - - using CustomPlayerEffects; + using Exiled.API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Scp330; using Exiled.Events.Handlers; - using HarmonyLib; - using Interactables.Interobjects; - - using InventorySystem; using InventorySystem.Items.Usables.Scp330; using static HarmonyLib.AccessTools; @@ -79,69 +73,25 @@ private static IEnumerable Transpiler(IEnumerable instruction.Calls(Method(typeof(Scp330Bag), nameof(Scp330Bag.ServerProcessPickup)))) + removeServerProcessOffset; - - newInstructions.RemoveRange(removeServerProcessIndex, 3); - - // Replace NW server process logic. - newInstructions.InsertRange( - removeServerProcessIndex, - new[] - { - // ldarg.1 is already in the stack - - // ev.Candy - new CodeInstruction(OpCodes.Ldloc, ev), - new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(InteractingScp330EventArgs), nameof(InteractingScp330EventArgs.Candy))), - - // bag - new CodeInstruction(OpCodes.Ldloca_S, 3), - - // ServerProcessPickup(ReferenceHub, CandyKindID, Scp330Bag) - new CodeInstruction(OpCodes.Call, Method(typeof(InteractingScp330), nameof(ServerProcessPickup), new[] { typeof(ReferenceHub), typeof(CandyKindID), typeof(Scp330Bag).MakeByRefType() })), - }); - // This is to find the location of RpcMakeSound to remove the original code and add a new sever logic structure (Start point) int addShouldSeverOffset = 1; int addShouldSeverIndex = newInstructions.FindLastIndex( instruction => instruction.Calls(Method(typeof(Scp330Interobject), nameof(Scp330Interobject.RpcMakeSound)))) + addShouldSeverOffset; - // This is to find the location of the next return (End point) - int includeSameLine = 1; - int nextReturn = newInstructions.FindIndex(addShouldSeverIndex, instruction => instruction.opcode == OpCodes.Ret) + includeSameLine; - Label originalLabel = newInstructions[addShouldSeverIndex].ExtractLabels()[0]; - - // Remove original code from after RpcMakeSound to next return and then fully replace it. - newInstructions.RemoveRange(addShouldSeverIndex, nextReturn - addShouldSeverIndex); - - addShouldSeverIndex = newInstructions.FindLastIndex( - instruction => instruction.Calls(Method(typeof(Scp330Interobject), nameof(Scp330Interobject.RpcMakeSound)))) + addShouldSeverOffset; + int serverEffectLocationStart = -1; + int enableEffect = newInstructions.FindLastIndex( + instruction => instruction.LoadsField(Field(typeof(PlayerEffectsController), nameof(ReferenceHub.playerEffectsController)))) + serverEffectLocationStart; newInstructions.InsertRange( addShouldSeverIndex, - new CodeInstruction[] + new[] { // if (!ev.ShouldSever) // goto shouldNotSever; - new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex).WithLabels(originalLabel), + new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), new(OpCodes.Callvirt, PropertyGetter(typeof(InteractingScp330EventArgs), nameof(InteractingScp330EventArgs.ShouldSever))), new(OpCodes.Brfalse, shouldNotSever), - - // ev.Player.EnableEffect("SevereHands", 1, 0f, false) - new(OpCodes.Ldloc, ev.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(InteractingScp330EventArgs), nameof(InteractingScp330EventArgs.Player))), - new(OpCodes.Ldstr, nameof(SeveredHands)), - new(OpCodes.Ldc_I4_1), - new(OpCodes.Ldc_R4, 0f), - new(OpCodes.Ldc_I4_0), - new(OpCodes.Callvirt, Method(typeof(Player), nameof(Player.EnableEffect), new[] { typeof(string), typeof(byte), typeof(float), typeof(bool) })), - new(OpCodes.Pop), - - // return; - new(OpCodes.Ret), + new(OpCodes.Br, enableEffect), }); // This will let us jump to the taken candies code and lock until ldarg_0, meaning we allow base game logic handle candy adding. @@ -157,28 +107,5 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - - private static bool ServerProcessPickup(ReferenceHub player, CandyKindID candy, out Scp330Bag bag) - { - if (!Scp330Bag.TryGetBag(player, out bag)) - { - player.inventory.ServerAddItem(ItemType.SCP330); - - if (!Scp330Bag.TryGetBag(player, out bag)) - return false; - - bag.Candies = new List { candy }; - bag.ServerRefreshBag(); - - return true; - } - - bool result = bag.TryAddSpecific(candy); - - if (bag.AcquisitionAlreadyReceived) - bag.ServerRefreshBag(); - - return result; - } } } \ No newline at end of file From e39238e62841784b73c5fa917b3473b3e4b037ab Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:53:39 +0200 Subject: [PATCH 3/9] IScp330Event (#11) Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> --- .../EventArgs/Interfaces/IHazardEvent.cs | 2 +- .../EventArgs/Interfaces/IScp330Event.cs | 22 +++++++++++++++++++ .../EventArgs/Interfaces/IUsableEvent.cs | 4 ++-- .../Scp330/DroppingScp330EventArgs.cs | 9 +++++--- .../EventArgs/Scp330/EatenScp330EventArgs.cs | 14 +++++++++--- .../EventArgs/Scp330/EatingScp330EventArgs.cs | 16 ++++++++++---- .../Scp330/InteractingScp330EventArgs.cs | 14 ++++++++++-- .../Patches/Events/Scp330/EatingScp330.cs | 10 +++++++-- 8 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 EXILED/Exiled.Events/EventArgs/Interfaces/IScp330Event.cs diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IHazardEvent.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IHazardEvent.cs index 54f6a3f74..d10d34631 100644 --- a/EXILED/Exiled.Events/EventArgs/Interfaces/IHazardEvent.cs +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IHazardEvent.cs @@ -10,7 +10,7 @@ namespace Exiled.Events.EventArgs.Interfaces using Exiled.API.Features.Hazards; /// - /// Event args for all related events. + /// Event args for all related events. /// public interface IHazardEvent : IExiledEvent { diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp330Event.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp330Event.cs new file mode 100644 index 000000000..45a3072e7 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp330Event.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Interfaces +{ + using Exiled.API.Features.Items; + + /// + /// Event args used for all related events. + /// + public interface IScp330Event : IItemEvent + { + /// + /// Gets the triggering the event. + /// + public Scp330 Scp330 { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IUsableEvent.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IUsableEvent.cs index 6c34c16e8..67b28f5b6 100644 --- a/EXILED/Exiled.Events/EventArgs/Interfaces/IUsableEvent.cs +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IUsableEvent.cs @@ -10,12 +10,12 @@ namespace Exiled.Events.EventArgs.Interfaces using Exiled.API.Features.Items; /// - /// Event args used for all related events. + /// Event args used for all related events. /// public interface IUsableEvent : IItemEvent { /// - /// Gets the triggering the event. + /// Gets the triggering the event. /// public Usable Usable { get; } } diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs index 5aabbd226..0105cc1a1 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs @@ -17,7 +17,7 @@ namespace Exiled.Events.EventArgs.Scp330 /// /// Contains all information before a player drops a SCP-330 candy. /// - public class DroppingScp330EventArgs : IPlayerEvent, IDeniableEvent + public class DroppingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableEvent { /// /// Initializes a new instance of the class. @@ -39,9 +39,12 @@ public DroppingScp330EventArgs(Player player, Scp330Bag scp330, CandyKindID cand } /// - /// Gets or sets a value representing the being picked up. + /// Gets or sets a value representing the being picked up. /// - public Scp330 Scp330 { get; set; } + public Scp330 Scp330 { get; set; } // Todo Remove set + + /// + public Item Item => Scp330; /// /// Gets or sets a value indicating whether or not the type of candy drop. diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/EatenScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/EatenScp330EventArgs.cs index 3fa09415b..29beac357 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/EatenScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/EatenScp330EventArgs.cs @@ -8,7 +8,7 @@ namespace Exiled.Events.EventArgs.Scp330 { using API.Features; - + using Exiled.API.Features.Items; using Interfaces; using InventorySystem.Items.Usables.Scp330; @@ -16,16 +16,18 @@ namespace Exiled.Events.EventArgs.Scp330 /// /// Contains all information after a player has eaten SCP-330. /// - public class EatenScp330EventArgs : IPlayerEvent + public class EatenScp330EventArgs : IPlayerEvent, IScp330Event { /// /// Initializes a new instance of the class. /// /// . + /// . /// . - public EatenScp330EventArgs(Player player, ICandy candy) + public EatenScp330EventArgs(Player player, Scp330Bag scp330, ICandy candy) { Player = player; + Scp330 = (Scp330)Item.Get(scp330); Candy = candy; } @@ -38,5 +40,11 @@ public EatenScp330EventArgs(Player player, ICandy candy) /// Gets the player who has eaten SCP-330. /// public Player Player { get; } + + /// + public Scp330 Scp330 { get; } + + /// + public Item Item => Scp330; } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/EatingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/EatingScp330EventArgs.cs index d24a57ad4..825f8424b 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/EatingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/EatingScp330EventArgs.cs @@ -8,7 +8,7 @@ namespace Exiled.Events.EventArgs.Scp330 { using API.Features; - + using Exiled.API.Features.Items; using Interfaces; using InventorySystem.Items.Usables.Scp330; @@ -16,17 +16,19 @@ namespace Exiled.Events.EventArgs.Scp330 /// /// Contains all information before a player eats SCP-330. /// - public class EatingScp330EventArgs : IPlayerEvent, IDeniableEvent + public class EatingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableEvent { /// /// Initializes a new instance of the class. /// /// . - /// . + /// . + /// . /// . - public EatingScp330EventArgs(Player player, ICandy candy, bool isAllowed = true) + public EatingScp330EventArgs(Player player, Scp330Bag scp330, ICandy candy, bool isAllowed = true) { Player = player; + Scp330 = (Scp330)Item.Get(scp330); Candy = candy; IsAllowed = isAllowed; } @@ -45,5 +47,11 @@ public EatingScp330EventArgs(Player player, ICandy candy, bool isAllowed = true) /// Gets the player who's eating SCP-330. /// public Player Player { get; } + + /// + public Scp330 Scp330 { get; } + + /// + public Item Item => Scp330; } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs index 30cd0a1ac..b87238842 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs @@ -8,15 +8,16 @@ namespace Exiled.Events.EventArgs.Scp330 { using API.Features; - + using Exiled.API.Features.Items; using Interfaces; using InventorySystem.Items.Usables.Scp330; + using YamlDotNet.Core.Tokens; /// /// Contains all information before a player interacts with SCP-330. /// - public class InteractingScp330EventArgs : IPlayerEvent, IDeniableEvent + public class InteractingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableEvent { /// /// Initializes a new instance of the class. @@ -30,6 +31,7 @@ public class InteractingScp330EventArgs : IPlayerEvent, IDeniableEvent public InteractingScp330EventArgs(Player player, int usage) { Player = player; + Scp330 = Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag) ? (Scp330)Item.Get(scp330Bag) : null; Candy = Scp330Candies.GetRandom(); UsageCount = usage; ShouldSever = usage >= 2; @@ -60,5 +62,13 @@ public InteractingScp330EventArgs(Player player, int usage) /// Gets the triggering the event. /// public Player Player { get; } + + /// + /// This value can be null. + public Scp330 Scp330 { get; } + + /// + /// This value can be null. + public Item Item => Scp330; } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Scp330/EatingScp330.cs b/EXILED/Exiled.Events/Patches/Events/Scp330/EatingScp330.cs index 1ad59414d..f52435bdd 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp330/EatingScp330.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp330/EatingScp330.cs @@ -51,13 +51,16 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Date: Tue, 6 Aug 2024 22:55:00 +0200 Subject: [PATCH 4/9] PR than i made (#9) * PR made by me Thanks Ika for the Help on CustomAmmo/CategoryLimit Co-Authored-By: Ika <36999341+IkaOverride@users.noreply.github.com> * Build error * Added support to SCPs for escaping-related events. * spacing * LocalReporting Exiled should be call before NWAPI * Optimising / More documentation on SpawningItem * ISpawnableScp * Use of ComponentsEqualityComparer for Dictionary --------- Co-authored-by: Ika <36999341+IkaOverride@users.noreply.github.com> --- .../Exiled.API/Extensions/MirrorExtensions.cs | 9 +- EXILED/Exiled.API/Features/Camera.cs | 2 +- .../Features/Doors/AirlockController.cs | 2 +- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- EXILED/Exiled.API/Features/Generator.cs | 2 +- EXILED/Exiled.API/Features/Hazards/Hazard.cs | 2 +- EXILED/Exiled.API/Features/Items/Ammo.cs | 2 +- EXILED/Exiled.API/Features/Items/Item.cs | 2 +- EXILED/Exiled.API/Features/Lift.cs | 2 +- EXILED/Exiled.API/Features/Player.cs | 173 +++++++++++++++++- EXILED/Exiled.API/Features/Ragdoll.cs | 2 +- EXILED/Exiled.API/Features/Recontainer.cs | 5 + EXILED/Exiled.API/Features/Roles/FpcRole.cs | 16 ++ .../Exiled.API/Features/Roles/Scp049Role.cs | 2 +- .../Exiled.API/Features/Roles/Scp079Role.cs | 3 +- .../Exiled.API/Features/Roles/Scp096Role.cs | 3 +- .../Exiled.API/Features/Roles/Scp106Role.cs | 3 +- .../Exiled.API/Features/Roles/Scp173Role.cs | 3 +- .../Exiled.API/Features/Roles/Scp3114Role.cs | 2 +- .../Exiled.API/Features/Roles/Scp939Role.cs | 3 +- EXILED/Exiled.API/Features/Room.cs | 2 +- EXILED/Exiled.API/Features/TeslaGate.cs | 2 +- EXILED/Exiled.API/Features/Window.cs | 2 +- .../Patches/Events/Map/SpawningItem.cs | 16 +- .../Patches/Events/Player/Escaping.cs | 27 +-- .../Patches/Events/Server/Reporting.cs | 4 +- .../Patches/Generic/GetCustomAmmoLimit.cs | 35 ++++ .../Patches/Generic/GetCustomCategoryLimit.cs | 34 ++++ 28 files changed, 308 insertions(+), 54 deletions(-) create mode 100644 EXILED/Exiled.Events/Patches/Generic/GetCustomAmmoLimit.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/GetCustomCategoryLimit.cs 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); + } + } +} From eb046ac0693bd9359583ebfa678e6b59d903da90 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:55:12 +0200 Subject: [PATCH 5/9] [Events] Fix null reference (#15) * fix situation when `ply == null` * lol why --------- Co-authored-by: IRacle --- .../EventArgs/Player/ChangingItemEventArgs.cs | 2 +- .../Patches/Events/Player/Healing.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs index 403f5f705..2265309e4 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs @@ -46,7 +46,7 @@ public Item Item get => newItem; set { - if (!Player.Inventory.UserInventory.Items.TryGetValue(value.Serial, out _)) + if (value != null && !Player.Inventory.UserInventory.Items.TryGetValue(value.Serial, out _)) throw new InvalidOperationException("ev.NewItem cannot be set to an item they do not have."); newItem = value; diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Healing.cs b/EXILED/Exiled.Events/Patches/Events/Player/Healing.cs index 6f17fad14..5acbce087 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Healing.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Healing.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Exiled Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -34,6 +34,8 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); Label continueLabel = generator.DefineLabel(); + Label skipHealing = generator.DefineLabel(); + Label skipHealed = generator.DefineLabel(); LocalBuilder ev = generator.DeclareLocal(typeof(HealingEventArgs)); LocalBuilder player = generator.DeclareLocal(typeof(Player)); @@ -48,10 +50,14 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Date: Tue, 6 Aug 2024 14:59:04 -0600 Subject: [PATCH 6/9] Offline mode support (#19) * Fix Offline-mode breaking everything * Add `offline` authentication type and append `@offline` to UserIds during offline mode * Add offline id support to Player.Get * Comment transpilers --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Enums/AuthenticationType.cs | 5 + .../Exiled.API/Extensions/StringExtensions.cs | 6 +- EXILED/Exiled.API/Features/Player.cs | 3 +- .../Patches/Events/Player/Verified.cs | 56 +++++- .../Patches/Generic/OfflineModeIds.cs | 164 ++++++++++++++++++ 5 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 EXILED/Exiled.Events/Patches/Generic/OfflineModeIds.cs diff --git a/EXILED/Exiled.API/Enums/AuthenticationType.cs b/EXILED/Exiled.API/Enums/AuthenticationType.cs index 9de49f902..0590ea097 100644 --- a/EXILED/Exiled.API/Enums/AuthenticationType.cs +++ b/EXILED/Exiled.API/Enums/AuthenticationType.cs @@ -42,5 +42,10 @@ public enum AuthenticationType /// Indicates that the player has been authenticated as DedicatedServer. /// DedicatedServer, + + /// + /// Indicates that the player has been authenticated during Offline mode. + /// + Offline, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/StringExtensions.cs b/EXILED/Exiled.API/Extensions/StringExtensions.cs index 69b184bc6..37d1a6777 100644 --- a/EXILED/Exiled.API/Extensions/StringExtensions.cs +++ b/EXILED/Exiled.API/Extensions/StringExtensions.cs @@ -161,7 +161,11 @@ public static string GetBefore(this string input, char symbol) /// /// The user id. /// Returns the raw user id. - public static string GetRawUserId(this string userId) => userId.Substring(0, userId.LastIndexOf('@')); + public static string GetRawUserId(this string userId) + { + int index = userId.IndexOf('@'); + return index == -1 ? userId : userId.Substring(0, index); + } /// /// Gets a SHA256 hash of a player's user id without the authentication. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 9172aab3e..f7403cdb6 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -292,6 +292,7 @@ public AuthenticationType AuthenticationType "northwood" => AuthenticationType.Northwood, "localhost" => AuthenticationType.LocalHost, "ID_Dedicated" => AuthenticationType.DedicatedServer, + "offline" => AuthenticationType.Offline, _ => AuthenticationType.Unknown, }; } @@ -1310,7 +1311,7 @@ public static Player Get(string args) if (int.TryParse(args, out int id)) return Get(id); - if (args.EndsWith("@steam") || args.EndsWith("@discord") || args.EndsWith("@northwood")) + if (args.EndsWith("@steam") || args.EndsWith("@discord") || args.EndsWith("@northwood") || args.EndsWith("@offline")) { foreach (Player player in Dictionary.Values) { diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Verified.cs b/EXILED/Exiled.Events/Patches/Events/Player/Verified.cs index 585457a43..ce6697b5d 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Verified.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Verified.cs @@ -7,16 +7,20 @@ namespace Exiled.Events.Patches.Events.Player { +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter using System; + using System.Collections.Generic; + using System.Reflection.Emit; using API.Features; + using API.Features.Pools; using CentralAuth; using Exiled.API.Extensions; using Exiled.Events.EventArgs.Player; - using HarmonyLib; -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + using static HarmonyLib.AccessTools; /// /// Patches . @@ -25,12 +29,16 @@ namespace Exiled.Events.Patches.Events.Player [HarmonyPatch(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.FinalizeAuthentication))] internal static class Verified { - private static void Postfix(PlayerAuthenticationManager __instance) + /// + /// Called after the player has been verified. + /// + /// The player's hub. + internal static void PlayerVerified(ReferenceHub hub) { - if (!Player.UnverifiedPlayers.TryGetValue(__instance._hub.gameObject, out Player player)) - Joined.CallEvent(__instance._hub, out player); + if (!Player.UnverifiedPlayers.TryGetValue(hub.gameObject, out Player player)) + Joined.CallEvent(hub, out player); - Player.Dictionary.Add(__instance._hub.gameObject, player); + Player.Dictionary.Add(hub.gameObject, player); player.IsVerified = true; player.RawUserId = player.UserId.GetRawUserId(); @@ -39,5 +47,41 @@ private static void Postfix(PlayerAuthenticationManager __instance) Handlers.Player.OnVerified(new VerifiedEventArgs(player)); } + + private static void Postfix(PlayerAuthenticationManager __instance) + { + PlayerVerified(__instance._hub); + } + } + + /// + /// Patches . + /// Adds the event during offline mode. + /// + [HarmonyPatch(typeof(NicknameSync), nameof(NicknameSync.UserCode_CmdSetNick__String))] + internal static class VerifiedOfflineMode + { + private static IEnumerable Transpiler(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + const int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Callvirt && x.OperandIs(Method(typeof(CharacterClassManager), nameof(CharacterClassManager.SyncServerCmdBinding)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Verified.PlayerVerified(this._hub); + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, Field(typeof(NicknameSync), nameof(NicknameSync._hub))), + new CodeInstruction(OpCodes.Call, Method(typeof(Verified), nameof(Verified.PlayerVerified))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/OfflineModeIds.cs b/EXILED/Exiled.Events/Patches/Generic/OfflineModeIds.cs new file mode 100644 index 000000000..46c412dc3 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/OfflineModeIds.cs @@ -0,0 +1,164 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ +#pragma warning disable SA1402 // File may only contain a single type + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CentralAuth; + using HarmonyLib; + using PluginAPI.Core.Interfaces; + using PluginAPI.Events; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to add an @offline suffix to UserIds in Offline Mode. + /// + [HarmonyPatch(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.Start))] + internal static class OfflineModeIds + { + private static IEnumerable Transpiler(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + const int offset = -1; + int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Call && instruction.OperandIs(PropertySetter(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.UserId)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + new CodeInstruction(OpCodes.Call, Method(typeof(OfflineModeIds), nameof(BuildUserId))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + + private static string BuildUserId(string userId) => $"{userId}@offline"; + } + + /// + /// Patches to add the player's UserId to the dictionary. + /// + [HarmonyPatch(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.Start))] + internal static class OfflineModePlayerIds + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label skipLabel = generator.DefineLabel(); + + const int offset = 1; + int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Call && instruction.OperandIs(PropertySetter(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.UserId)))) + offset; + + // if (!Player.PlayersUserIds.ContainsKey(this.UserId)) + // Player.PlayersUserIds.Add(this.UserId, this._hub); + newInstructions.InsertRange( + index, + new[] + { + // if (Player.PlayersUserIds.ContainsKey(this.UserId)) goto skip; + new(OpCodes.Ldsfld, Field(typeof(PluginAPI.Core.Player), nameof(PluginAPI.Core.Player.PlayersUserIds))), + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.UserId))), + new(OpCodes.Callvirt, Method(typeof(Dictionary), nameof(Dictionary.ContainsKey))), + new(OpCodes.Brtrue_S, skipLabel), + + // Player.PlayersUserIds.Add(this.UserId, this._hub); + new(OpCodes.Ldsfld, Field(typeof(PluginAPI.Core.Player), nameof(PluginAPI.Core.Player.PlayersUserIds))), + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager.UserId))), + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager._hub))), + new(OpCodes.Callvirt, Method(typeof(Dictionary), nameof(Dictionary.Add))), + + // skip: + new CodeInstruction(OpCodes.Nop).WithLabels(skipLabel), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches to prevent it from executing the event when the server is in offline mode. + /// + [HarmonyPatch(typeof(ReferenceHub), nameof(ReferenceHub.Start))] + internal static class OfflineModeReferenceHub + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + const int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Callvirt) + offset; + + Label returnLabel = generator.DefineLabel(); + + newInstructions.InsertRange( + index, + new[] + { + new CodeInstruction(OpCodes.Br_S, returnLabel), + }); + + newInstructions[newInstructions.Count - 1].WithLabels(returnLabel); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches to execute the event when the server is in offline mode. + /// + [HarmonyPatch(typeof(NicknameSync), nameof(NicknameSync.UserCode_CmdSetNick__String))] + internal static class OfflineModeJoin + { + private static IEnumerable Transpiler(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + const int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Callvirt && x.OperandIs(Method(typeof(CharacterClassManager), nameof(CharacterClassManager.SyncServerCmdBinding)))) + offset; + + // EventManager.ExecuteEvent(new PlayerJoinedEvent(this._hub)); + newInstructions.InsertRange( + index, + new[] + { + // EventManager.ExecuteEvent(new PlayerJoinedEvent(this._hub)); + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, Field(typeof(NicknameSync), nameof(NicknameSync._hub))), + new CodeInstruction(OpCodes.Call, Method(typeof(OfflineModeJoin), nameof(ExecuteNwEvent))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + + private static void ExecuteNwEvent(ReferenceHub hub) + { + EventManager.ExecuteEvent(new PlayerJoinedEvent(hub)); + } + } +} \ No newline at end of file From 259cabb149861216517fde655fed91095e99f951 Mon Sep 17 00:00:00 2001 From: sky <99112969+skyyt15@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:00:44 +0200 Subject: [PATCH 7/9] Fix NW bugs (#32) * Fix Armor Drop from https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/230 * add comments * fix scp173 and adding bug report link to summary class fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 * Moved patches and fixed Scp173FirstKillPatch * Add Slowness Fix Avoid values more than 100 for effect slowness to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/378 * skill issue * skill issue (again= --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Patches/Fixes/ArmorDropPatch.cs | 55 +++++++++++++++++++ .../Patches/Fixes/Scp173FirstKillPatch.cs | 55 +++++++++++++++++++ .../Patches/Fixes/Scp173SecondKillPatch.cs | 54 ++++++++++++++++++ .../Patches/Fixes/SlownessFix.cs | 2 +- 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 EXILED/Exiled.Events/Patches/Fixes/ArmorDropPatch.cs create mode 100644 EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs create mode 100644 EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs diff --git a/EXILED/Exiled.Events/Patches/Fixes/ArmorDropPatch.cs b/EXILED/Exiled.Events/Patches/Fixes/ArmorDropPatch.cs new file mode 100644 index 000000000..a924f6808 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/ArmorDropPatch.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using HarmonyLib; + using InventorySystem; + using InventorySystem.Items; + using InventorySystem.Items.Armor; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/230 bug. + /// + [HarmonyPatch(typeof(BodyArmorUtils), nameof(BodyArmorUtils.RemoveEverythingExceedingLimits))] + internal static class ArmorDropPatch + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label continueLabel = generator.DefineLabel(); + int continueIndex = newInstructions.FindIndex(x => x.Is(OpCodes.Call, Method(typeof(Dictionary.Enumerator), nameof(Dictionary.Enumerator.MoveNext)))) - 1; + newInstructions[continueIndex].WithLabels(continueLabel); + + // before: if (keyValuePair.Value.Category != ItemCategory.Armor) + // after: if (keyValuePair.Value.Category != ItemCategory.Armor && keyValuePair.Value.Category != ItemCategory.None) + int index = newInstructions.FindIndex(x => x.Is(OpCodes.Ldc_I4_S, 9)); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // && keyValuePair.Value.Category != ItemCategory.None) + new(OpCodes.Ldloca_S, 4), + new(OpCodes.Call, PropertyGetter(typeof(KeyValuePair), nameof(KeyValuePair.Value))), + new(OpCodes.Ldfld, Field(typeof(ItemBase), nameof(ItemBase.Category))), + new(OpCodes.Ldc_I4_0), + new(OpCodes.Beq_S, continueLabel), + }); + + 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/Fixes/Scp173FirstKillPatch.cs b/EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs new file mode 100644 index 000000000..f66d35058 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CustomPlayerEffects; + using HarmonyLib; + using PlayerRoles.PlayableScps.Scp173; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 bug. + /// + [HarmonyPatch(typeof(Scp173SnapAbility), nameof(Scp173SnapAbility.TryHitTarget))] + internal static class Scp173FirstKillPatch + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label continueLabel = generator.DefineLabel(); + + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldc_I4_0); + newInstructions[index].WithLabels(continueLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // if (hitboxIdentity.TargetHub.playerEffectController.GetEffect().IsEnabled) return false; + new(OpCodes.Ldloc_2), + new(OpCodes.Callvirt, PropertyGetter(typeof(HitboxIdentity), nameof(HitboxIdentity.TargetHub))), + new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.playerEffectsController))), + new(OpCodes.Callvirt, Method(typeof(PlayerEffectsController), nameof(PlayerEffectsController.GetEffect), generics: new[] { typeof(SpawnProtected) })), + new(OpCodes.Callvirt, PropertyGetter(typeof(StatusEffectBase), nameof(StatusEffectBase.IsEnabled))), + new(OpCodes.Brfalse_S, continueLabel), + new(OpCodes.Ldc_I4_0), + new(OpCodes.Ret), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs b/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs new file mode 100644 index 000000000..2b6dcb028 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CustomPlayerEffects; + using HarmonyLib; + using Mirror; + using PlayerRoles.PlayableScps.Scp173; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 bug. + /// + [HarmonyPatch(typeof(Scp173TeleportAbility), nameof(Scp173TeleportAbility.ServerProcessCmd))] + internal static class Scp173SecondKillPatch + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label returnLabel = generator.DefineLabel(); + + newInstructions[newInstructions.Count - 1].WithLabels(returnLabel); + + int offset = -5; + int index = newInstructions.FindIndex(x => x.Is(OpCodes.Callvirt, Method(typeof(MovementTracer), nameof(MovementTracer.GenerateBounds)))) + offset; + + newInstructions.InsertRange(index, new[] + { + // if (hub.playerEffectController.GetEffect().IsEnabled) return; + new CodeInstruction(OpCodes.Ldloc_S, 5).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.playerEffectsController))), + new(OpCodes.Callvirt, Method(typeof(PlayerEffectsController), nameof(PlayerEffectsController.GetEffect), generics: new[] { typeof(SpawnProtected) })), + new(OpCodes.Callvirt, PropertyGetter(typeof(StatusEffectBase), nameof(StatusEffectBase.IsEnabled))), + new(OpCodes.Brtrue_S, returnLabel), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Fixes/SlownessFix.cs b/EXILED/Exiled.Events/Patches/Fixes/SlownessFix.cs index e2e69b756..5a05c31a0 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/SlownessFix.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/SlownessFix.cs @@ -62,4 +62,4 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} +} \ No newline at end of file From 26d1fcc0d791fd036d4e28fdeb1f63235aeb26ec Mon Sep 17 00:00:00 2001 From: x3rt Date: Wed, 7 Aug 2024 01:54:44 -0600 Subject: [PATCH 8/9] Useless Event (939 Placed Amnestic Cloud) (#40) --- .../Scp939/PlacedAmnesticCloudEventArgs.cs | 51 +++++++++++++ EXILED/Exiled.Events/Handlers/Scp939.cs | 11 +++ .../Events/Scp939/PlacedAmnesticCloud.cs | 73 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp939/PlacedAmnesticCloud.cs diff --git a/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs new file mode 100644 index 000000000..107a7c869 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp939 +{ + using API.Features; + using API.Features.Hazards; + using Interfaces; + using PlayerRoles.PlayableScps.Scp939; + + using Scp939Role = API.Features.Roles.Scp939Role; + + /// + /// Contains all information after SCP-939 used its amnestic cloud ability. + /// + public class PlacedAmnesticCloudEventArgs : IScp939Event + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public PlacedAmnesticCloudEventArgs(ReferenceHub hub, Scp939AmnesticCloudInstance cloud) + { + Player = Player.Get(hub); + AmnesticCloud = new AmnesticCloudHazard(cloud); + Scp939 = Player.Role.As(); + } + + /// + /// Gets the player who's controlling SCP-939. + /// + public Player Player { get; } + + /// + /// Gets the instance. + /// + public AmnesticCloudHazard AmnesticCloud { get; } + + /// + public Scp939Role Scp939 { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Scp939.cs b/EXILED/Exiled.Events/Handlers/Scp939.cs index 14d1245c6..e82e94a80 100644 --- a/EXILED/Exiled.Events/Handlers/Scp939.cs +++ b/EXILED/Exiled.Events/Handlers/Scp939.cs @@ -32,6 +32,11 @@ public static class Scp939 /// public static Event PlacingAmnesticCloud { get; set; } = new(); + /// + /// Invoked after SCP-939 used its amnestic cloud ability. + /// + public static Event PlacedAmnesticCloud { get; set; } = new(); + /// /// Invoked before SCP-939 plays a stolen voice. /// @@ -81,6 +86,12 @@ public static class Scp939 /// The instance. public static void OnPlacingAmnesticCloud(PlacingAmnesticCloudEventArgs ev) => PlacingAmnesticCloud.InvokeSafely(ev); + /// + /// Called after SCP-939 used its amnestic cloud ability. + /// + /// The instance. + public static void OnPlacedAmnesticCloud(PlacedAmnesticCloudEventArgs ev) => PlacedAmnesticCloud.InvokeSafely(ev); + /// /// Called before SCP-939 plays a stolen voice. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Scp939/PlacedAmnesticCloud.cs b/EXILED/Exiled.Events/Patches/Events/Scp939/PlacedAmnesticCloud.cs new file mode 100644 index 000000000..ac224990f --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp939/PlacedAmnesticCloud.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp939 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp939; + using Exiled.Events.Handlers; + using HarmonyLib; + using PlayerRoles.PlayableScps.Scp939; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add the event. + /// + [EventPatch(typeof(Scp939), nameof(Scp939.PlacedAmnesticCloud))] + [HarmonyPatch(typeof(Scp939AmnesticCloudAbility), nameof(Scp939AmnesticCloudAbility.OnStateEnabled))] + internal static class PlacedAmnesticCloud + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder cloud = generator.DeclareLocal(typeof(Scp939AmnesticCloudInstance)); + + const int offset = -2; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Callvirt && x.OperandIs(Method(typeof(Scp939AmnesticCloudInstance), nameof(Scp939AmnesticCloudInstance.ServerSetup)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Scp939AmnesticCloudInstance cloud = Object.Instantiate(this._instancePrefab) + new CodeInstruction(OpCodes.Dup), + new CodeInstruction(OpCodes.Stloc_S, cloud), + }); + + index = newInstructions.Count - 1; + + // Scp939.OnPlacedAmnesticCloud(new PlacedAmnesticCloudEventArgs(this.Owner, cloud)); + newInstructions.InsertRange( + index, + new[] + { + // this.Owner + new CodeInstruction(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp939AmnesticCloudAbility), nameof(Scp939AmnesticCloudAbility.Owner))), + + // cloud + new CodeInstruction(OpCodes.Ldloc_S, cloud), + + // Scp939.OnPlacedAmnesticCloud(new PlacedAmnesticCloudEventArgs(this.Owner, cloud)); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(PlacedAmnesticCloudEventArgs))[0]), + new(OpCodes.Call, Method(typeof(Scp939), nameof(Scp939.OnPlacedAmnesticCloud))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From 03d6bd7c06b55d132a6b58e8a3c3e6c0f4509dc4 Mon Sep 17 00:00:00 2001 From: TtroubleTT <121741230+TtroubleTT@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:23:59 -0700 Subject: [PATCH 9/9] Fix custom role classes giving custom ammo when they are not suppose to (#24) * Fix custom role classes giving custom ammmo when they are not suppose to * change to using EnumUtils * Moved Ammo additions into Inventory call delayed --- .../API/Features/CustomRole.cs | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 51e4ac008..61f88ec2c 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -537,6 +537,16 @@ public virtual void AddRole(Player player) Log.Debug($"{Name}: Adding {itemName} to inventory."); TryAddItem(player, itemName); } + + if (Ammo.Count > 0) + { + Log.Debug($"{Name}: Adding Ammo to {player.Nickname} inventory."); + foreach (AmmoType type in EnumUtils.Values) + { + if (type != AmmoType.None) + player.SetAmmo(type, Ammo.ContainsKey(type) ? Ammo[type] == ushort.MaxValue ? InventoryLimits.GetAmmoLimit(type.GetItemType(), player.ReferenceHub) : Ammo[type] : (ushort)0); + } + } }); Log.Debug($"{Name}: Setting health values."); @@ -910,25 +920,6 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) { RemoveRole(ev.Player); } - else if (Check(ev.Player)) - { - Log.Debug($"{Name}: Checking ammo stuff {Ammo.Count}"); - if (Ammo.Count > 0) - { - Log.Debug($"{Name}: Clearing ammo"); - ev.Ammo.Clear(); - Timing.CallDelayed( - 0.5f, - () => - { - foreach (AmmoType type in Enum.GetValues(typeof(AmmoType))) - { - if (type != AmmoType.None) - ev.Player.SetAmmo(type, Ammo.ContainsKey(type) ? Ammo[type] == ushort.MaxValue ? InventoryLimits.GetAmmoLimit(type.GetItemType(), ev.Player.ReferenceHub) : Ammo[type] : (ushort)0); - } - }); - } - } } private void OnSpawningRagdoll(SpawningRagdollEventArgs ev)