diff --git a/.github/README.md b/.github/README.md index 4da34bd0b..b901da96d 100644 --- a/.github/README.md +++ b/.github/README.md @@ -10,10 +10,10 @@
-[![Version](https://img.shields.io/github/v/release/Exiled-Official/EXILED?sort=semver&style=flat-square&color=8DBBE9&label=Version)]() +[![Version](https://img.shields.io/github/v/release/ExMod-Team/EXILED?sort=semver&style=flat-square&color=8DBBE9&label=Version)]() [![License](https://img.shields.io/badge/License-CC%20BY%E2%80%93SA%203.0-df967f?style=flat-square)]() -[![Contributors](https://img.shields.io/github/contributors-anon/Exiled-Official/EXILED?color=90E59A&style=flat-square&label=Contributors)]() -[![GitHub Issues](https://img.shields.io/github/issues/Exiled-Official/EXILED.svg?style=flat-square&label=Issues&color=d77982)](https://github.com/Exiled-Official/EXILED/issues) +[![Contributors](https://img.shields.io/github/contributors-anon/ExMod-Team/EXILED?color=90E59A&style=flat-square&label=Contributors)]() +[![GitHub Issues](https://img.shields.io/github/issues/ExMod-Team/EXILED.svg?style=flat-square&label=Issues&color=d77982)](https://github.com/ExMod-Team/EXILED/issues) [![Discord](https://img.shields.io/discord/656673194693885975?color=738adb&label=Discord&logo=discord&logoColor=white&style=flat-square)](https://discord.gg/PyUkWTg)
@@ -30,85 +30,85 @@ Localized READMEs

- English + English


- Русский + Русский


- 中文 + 中文


- Español + Español


- Polski + Polski


- Português-BR + Português-BR


- Italiano + Italiano


- Čeština + Čeština


- Dansk + Dansk


- Türkçe + Türkçe


- German + German


- Français + Français


- 한국어 + 한국어


- ไทย + ไทย
diff --git a/.github/labeler.yml b/.github/labeler.yml index 5098459b0..97048dd23 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -35,6 +35,6 @@ Localization: # Add the 'Localization' label - changed-files: - any-glob-to-any-file: EXILED/Localization/** # Any modifications to Localization -GitHub_Actions: # Add the 'GitHub' label +GitHub: # Add the 'GitHub' label - changed-files: - any-glob-to-any-file: .github/** # Any modifications to github related files diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..032dc22c6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ +## Description +**Describe the changes** + + +**What is the current behavior?** (You can also link to an open issue here) + + +**What is the new behavior?** (if this is a feature change) + + +**Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) + + +**Other information**: + +
+ +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Documentations +
+ +## Submission checklist + +- [ ] I have checked the project can be compiled +- [ ] I have tested my changes and it worked as expected + +### Patches (if there are any changes related to Harmony patches) +- [ ] I have checked no IL patching errors in the console + +### Other +- [ ] Still requires more testing diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 351cdd3be..df73ad9a5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ permissions: id-token: write env: - EXILED_REFERENCES_URL: https://Exiled-Official.github.io/SL-References/Dev.zip + EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Master.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7be77c41a..6f9bc0f85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://Exiled-Official.github.io/SL-References/Master.zip + EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Master.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References jobs: diff --git a/.github/workflows/labeler.yml b/.github/workflows/pull_request_opened.yml similarity index 72% rename from .github/workflows/labeler.yml rename to .github/workflows/pull_request_opened.yml index 0486bfaec..a54bed971 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/pull_request_opened.yml @@ -20,3 +20,10 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yml sync-labels: true + assign-author: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - uses: toshimaru/auto-author-assign@v2.1.1 diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index 72e45189e..3e5cf7c46 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -10,7 +10,7 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://Exiled-Official.github.io/SL-References/Master.zip + EXILED_REFERENCES_URL: https://ExMod-Team.github.io/SL-References/Master.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bcbe907cc..efdac4cc8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,9 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://Exiled-Official.github.io/SL-References/Dev.zip + EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References - EXILED_DLL_ARCHIVER_URL: https://github.com/Exiled-Official/EXILED-DLL-Archiver/releases/latest/download/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/ExMod-Team/EXILED-DLL-Archiver/releases/latest/download/EXILED-DLL-Archiver.exe jobs: build: 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/Enums/HazardType.cs b/EXILED/Exiled.API/Enums/HazardType.cs new file mode 100644 index 000000000..f05f8852f --- /dev/null +++ b/EXILED/Exiled.API/Enums/HazardType.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + using Exiled.API.Features.Hazards; + + /// + /// Unique identifier for a . + /// + public enum HazardType + { + /// + /// SCP-939 amnestic cloud. + /// + AmnesticCloud, + + /// + /// Sinkhole spawned at start of round. + /// + Sinkhole, + + /// + /// SCP-173 tantrum. + /// + Tantrum, + + /// + /// Should never happen + /// + Unknown, + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/UncuffReason.cs b/EXILED/Exiled.API/Enums/UncuffReason.cs new file mode 100644 index 000000000..6b3018fda --- /dev/null +++ b/EXILED/Exiled.API/Enums/UncuffReason.cs @@ -0,0 +1,30 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// Reasons that player gets uncuffed. + /// + public enum UncuffReason + { + /// + /// Uncuffed by a player. + /// + Player, + + /// + /// Uncuffed due to the distance between cuffer and target. + /// + OutOfRange, + + /// + /// Uncuffed due to the cuffer no longer alive. + /// + CufferDied, + } +} diff --git a/EXILED/Exiled.API/Extensions/BitwiseExtensions.cs b/EXILED/Exiled.API/Extensions/BitwiseExtensions.cs new file mode 100644 index 000000000..2f8473784 --- /dev/null +++ b/EXILED/Exiled.API/Extensions/BitwiseExtensions.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + + /// + /// Extensions for bitwise operations. + /// + public static class BitwiseExtensions + { + /// + /// Adds the specified flags to the given enum value. + /// + /// The type of the enum. + /// The enum value to add flags to. + /// The flags to add. + /// The enum value with the specified flags added. + public static T AddFlags(this T flags, params T[] newFlags) + where T : Enum => flags.ModifyFlags(true, newFlags); + + /// + /// Removes the specified flags from the given enum value. + /// + /// The type of the enum. + /// The enum value to remove flags from. + /// The flags to remove. + /// The enum value with the specified flags removed. + public static T RemoveFlags(this T flags, params T[] oldFlags) + where T : Enum => flags.ModifyFlags(false, oldFlags); + + /// + /// Sets the specified flag to the given value, default is true. + /// + /// The flags enum to modify. + /// The value to set the flag to. + /// The flags to modify. + /// The type of the enum. + /// The flags enum with the flag set to the given value. + public static T ModifyFlags(this T flags, bool value, params T[] changeFlags) + where T : Enum + { + long currentValue = Convert.ToInt64(flags); + + foreach (T flag in changeFlags) + { + long flagValue = Convert.ToInt64(flag); + + if (value) + currentValue |= flagValue; + else + currentValue &= ~flagValue; + } + + return (T)Enum.ToObject(typeof(T), currentValue); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 21fe13f72..d2afe9487 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -25,6 +25,7 @@ namespace Exiled.API.Extensions using PlayerRoles; using PlayerRoles.FirstPersonControl; using PlayerRoles.PlayableScps.Scp049.Zombies; + using PlayerRoles.Voice; using RelativePositioning; using Respawning; @@ -181,6 +182,18 @@ public static void PlayGunSound(this Player player, Vector3 position, ItemType i player.Connection.Send(message); } + /// + /// Sets that only the player can see. + /// + /// Only this player can see Display Text. + /// Text displayed to the player. + public static void SetIntercomDisplayTextForTargetOnly(this Player target, string text) => target.SendFakeSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText), text); + + /// + /// Resync . + /// + public static void ResetIntercomDisplayText() => ResyncSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText)); + /// /// Sets of a that only the player can see. /// @@ -460,12 +473,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/Extensions/RoleExtensions.cs b/EXILED/Exiled.API/Extensions/RoleExtensions.cs index e9186ba71..c674af1bf 100644 --- a/EXILED/Exiled.API/Extensions/RoleExtensions.cs +++ b/EXILED/Exiled.API/Extensions/RoleExtensions.cs @@ -140,18 +140,22 @@ public static SpawnLocation GetRandomSpawnLocation(this RoleTypeId roleType) return null; } + /// + /// Gets the starting of a . + /// + /// The . + /// The that the role receives on spawn. + public static InventoryRoleInfo GetInventory(this RoleTypeId role) + => StartingInventories.DefinedInventories.TryGetValue(role, out InventoryRoleInfo info) + ? info + : new(Array.Empty(), new()); + /// /// Gets the starting items of a . /// /// The . /// An of that the role receives on spawn. Will be empty for classes that do not spawn with items. - public static ItemType[] GetStartingInventory(this RoleTypeId roleType) - { - if (StartingInventories.DefinedInventories.TryGetValue(roleType, out InventoryRoleInfo info)) - return info.Items; - - return Array.Empty(); - } + public static ItemType[] GetStartingInventory(this RoleTypeId roleType) => GetInventory(roleType).Items; /// /// Gets the starting ammo of a . @@ -160,10 +164,9 @@ public static ItemType[] GetStartingInventory(this RoleTypeId roleType) /// An of that the role receives on spawn. Will be empty for classes that do not spawn with ammo. public static Dictionary GetStartingAmmo(this RoleTypeId roleType) { - if (StartingInventories.DefinedInventories.TryGetValue(roleType, out InventoryRoleInfo info)) - return info.Ammo.ToDictionary(kvp => kvp.Key.GetAmmoType(), kvp => kvp.Value); + InventoryRoleInfo info = roleType.GetInventory(); - return new(); + return info.Ammo.ToDictionary(kvp => kvp.Key.GetAmmoType(), kvp => kvp.Value); } } } \ 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/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..c06a13b9b 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -14,7 +14,9 @@ namespace Exiled.API.Features.Doors using Exiled.API.Enums; using Exiled.API.Extensions; using Exiled.API.Features.Core; + using Exiled.API.Features.Hazards; using Exiled.API.Interfaces; + using global::Hazards; using Interactables.Interobjects; using Interactables.Interobjects.DoorUtils; using MEC; @@ -38,7 +40,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. @@ -309,6 +311,31 @@ public static Door Get(DoorVariant doorVariant) return DoorVariantToDoor[doorVariant]; } + /// + /// Gets the by . + /// + /// The to convert into an door. + /// The specified type. + /// The door wrapper for the given . + public static T Get(DoorVariant doorVariant) + where T : Door => Get(doorVariant) as T; + + /// + /// Gets a given the specified . + /// + /// The to search for. + /// The with the given or if not found. + public static Door Get(DoorType doorType) => List.FirstOrDefault(x => x.Type == doorType); + + /// + /// Gets the by . + /// + /// The to convert into an door. + /// The specified type. + /// The door wrapper for the given . + public static T Get(DoorType doorType) + where T : Door => Get(doorType) as T; + /// /// Gets a given the specified name. /// @@ -320,6 +347,15 @@ public static Door Get(string name) return nameExtension is null ? null : Get(nameExtension.TargetDoor); } + /// + /// Gets the by . + /// + /// The name to search for. + /// The specified type. + /// The door wrapper for the given . + public static T Get(string name) + where T : Door => Get(name) as T; + /// /// Gets the door object associated with a specific , or creates a new one if there isn't one. /// @@ -327,20 +363,6 @@ public static Door Get(string name) /// The with the given name or if not found. public static Door Get(GameObject gameObject) => gameObject is null ? null : Get(gameObject.GetComponentInChildren()); - /// - /// Gets a of filtered based on a predicate. - /// - /// The condition to satify. - /// A of which contains elements that satify the condition. - public static IEnumerable Get(Func predicate) => List.Where(predicate); - - /// - /// Gets a given the specified . - /// - /// The to search for. - /// The with the given or if not found. - public static Door Get(DoorType doorType) => List.FirstOrDefault(x => x.Type == doorType); - /// /// Returns the closest to the given . /// @@ -367,6 +389,13 @@ public static Door Random(ZoneType type = ZoneType.Unspecified, bool onlyUnbroke return doors[UnityEngine.Random.Range(0, doors.Count)]; } + /// + /// Gets a of filtered based on a predicate. + /// + /// The condition to satify. + /// A of which contains elements that satify the condition. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + /// /// Locks all doors given the specified . /// 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/AmnesticCloudHazard.cs b/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs index c385c143f..31c9ad244 100644 --- a/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs @@ -3,10 +3,11 @@ // Copyright (c) Exiled Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. // -// ----------------------------------------------------------------------- +// ------------------------------------------------------------------------ namespace Exiled.API.Features.Hazards { + using Exiled.API.Enums; using PlayerRoles.PlayableScps.Scp939; /// @@ -14,6 +15,8 @@ namespace Exiled.API.Features.Hazards /// public class AmnesticCloudHazard : TemporaryHazard { + private static Scp939AmnesticCloudInstance amnesticCloudPrefab; + /// /// Initializes a new instance of the class. /// @@ -26,9 +29,26 @@ public AmnesticCloudHazard(Scp939AmnesticCloudInstance hazard) Owner = Player.Get(Ability.Owner); } + /// + /// Gets the amnestic cloud prefab. + /// + public static Scp939AmnesticCloudInstance AmnesticCloudPrefab + { + get + { + if (amnesticCloudPrefab == null) + amnesticCloudPrefab = PrefabHelper.GetPrefab(PrefabType.AmnesticCloudHazard); + + return amnesticCloudPrefab; + } + } + /// public new Scp939AmnesticCloudInstance Base { get; } + /// + public override HazardType Type => HazardType.AmnesticCloud; + /// /// Gets the for this instance. /// diff --git a/EXILED/Exiled.API/Features/Hazards/Hazard.cs b/EXILED/Exiled.API/Features/Hazards/Hazard.cs index 6861d1a68..09bd880d3 100644 --- a/EXILED/Exiled.API/Features/Hazards/Hazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/Hazard.cs @@ -11,6 +11,7 @@ namespace Exiled.API.Features.Hazards using System.Collections.Generic; using System.Linq; + using Exiled.API.Enums; using Exiled.API.Features.Core; using Exiled.API.Interfaces; using global::Hazards; @@ -25,7 +26,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. @@ -48,6 +49,11 @@ public Hazard(EnvironmentalHazard hazard) /// public EnvironmentalHazard Base { get; } + /// + /// Gets the associated with the current Hazard. + /// + public virtual HazardType Type { get; } = HazardType.Unknown; + /// /// Gets or sets the list with all affected by this hazard players. /// @@ -123,6 +129,15 @@ public static Hazard Get(EnvironmentalHazard environmentalHazard) => _ => new Hazard(environmentalHazard) }; + /// + /// Gets the by . + /// + /// The to convert into an hazard. + /// The specified type. + /// The hazard wrapper for the given . + public static T Get(EnvironmentalHazard environmentalHazard) + where T : Hazard => Get(environmentalHazard) as T; + /// /// Gets the hazard by the room where it's located. /// @@ -144,6 +159,13 @@ public static Hazard Get(EnvironmentalHazard environmentalHazard) => /// of based on predicate. public static IEnumerable Get(Func predicate) => List.Where(predicate); + /// + /// Gets an of . + /// + /// The to get. + /// of based on type. + public static IEnumerable Get(HazardType type) => Get(h => h.Type == type); + /// /// Checks if player is in hazard zone. /// diff --git a/EXILED/Exiled.API/Features/Hazards/SinkholeHazard.cs b/EXILED/Exiled.API/Features/Hazards/SinkholeHazard.cs index e8e0c4f3a..34cbaacd5 100644 --- a/EXILED/Exiled.API/Features/Hazards/SinkholeHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/SinkholeHazard.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features.Hazards { + using Exiled.API.Enums; using global::Hazards; /// @@ -28,5 +29,8 @@ public SinkholeHazard(SinkholeEnvironmentalHazard hazard) /// Gets the . /// public new SinkholeEnvironmentalHazard Base { get; } + + /// + public override HazardType Type => HazardType.Sinkhole; } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs b/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs index 15f54b5ac..906bd4faf 100644 --- a/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs @@ -7,7 +7,9 @@ namespace Exiled.API.Features.Hazards { + using Exiled.API.Enums; using global::Hazards; + using Mirror; using RelativePositioning; using UnityEngine; @@ -16,6 +18,8 @@ namespace Exiled.API.Features.Hazards /// public class TantrumHazard : TemporaryHazard { + private static TantrumEnvironmentalHazard tantrumPrefab; + /// /// Initializes a new instance of the class. /// @@ -26,11 +30,28 @@ public TantrumHazard(TantrumEnvironmentalHazard hazard) Base = hazard; } + /// + /// Gets the tantrum prefab. + /// + public static TantrumEnvironmentalHazard TantrumPrefab + { + get + { + if (tantrumPrefab == null) + tantrumPrefab = PrefabHelper.GetPrefab(PrefabType.TantrumObj); + + return tantrumPrefab; + } + } + /// /// Gets the . /// public new TantrumEnvironmentalHazard Base { get; } + /// + public override HazardType Type => HazardType.Tantrum; + /// /// Gets or sets a value indicating whether or not sizzle should be played. /// @@ -57,5 +78,28 @@ public Transform CorrectPosition get => Base._correctPosition; set => Base._correctPosition = value; } + + /// + /// Places a Tantrum (SCP-173's ability) in the indicated position. + /// + /// The position where you want to spawn the Tantrum. + /// Whether or not the tantrum will apply the effect. + /// If is , the tantrum is moved slightly up from its original position. Otherwise, the collision will not be detected and the slowness will not work. + /// The instance. + public static TantrumHazard PlaceTantrum(Vector3 position, bool isActive = true) + { + TantrumEnvironmentalHazard tantrum = Object.Instantiate(TantrumPrefab); + + if (!isActive) + tantrum.SynchronizedPosition = new(position); + else + tantrum.SynchronizedPosition = new(position + (Vector3.up * 0.25f)); + + tantrum._destroyed = !isActive; + + NetworkServer.Spawn(tantrum.gameObject); + + return Get(tantrum); + } } } \ No newline at end of file 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/ExplosiveGrenade.cs b/EXILED/Exiled.API/Features/Items/ExplosiveGrenade.cs index 8f9aa165c..0002d5d76 100644 --- a/EXILED/Exiled.API/Features/Items/ExplosiveGrenade.cs +++ b/EXILED/Exiled.API/Features/Items/ExplosiveGrenade.cs @@ -119,7 +119,7 @@ public ExplosionGrenadeProjectile SpawnActive(Vector3 position, Player owner = n ipb.Info = new PickupSyncInfo(Type, Weight, ItemSerialGenerator.GenerateNext()); - ExplosionGrenadeProjectile grenade = (ExplosionGrenadeProjectile)Pickup.Get(ipb); + ExplosionGrenadeProjectile grenade = Pickup.Get(ipb); grenade.Base.gameObject.SetActive(true); diff --git a/EXILED/Exiled.API/Features/Items/FlashGrenade.cs b/EXILED/Exiled.API/Features/Items/FlashGrenade.cs index e2d009ac7..979784f2c 100644 --- a/EXILED/Exiled.API/Features/Items/FlashGrenade.cs +++ b/EXILED/Exiled.API/Features/Items/FlashGrenade.cs @@ -100,7 +100,7 @@ public FlashbangProjectile SpawnActive(Vector3 position, Player owner = null) ipb.Info = new PickupSyncInfo(Type, Weight, ItemSerialGenerator.GenerateNext()); - FlashbangProjectile grenade = (FlashbangProjectile)Pickup.Get(ipb); + FlashbangProjectile grenade = Pickup.Get(ipb); grenade.Base.gameObject.SetActive(true); diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index fd0bba35a..96d02257f 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -25,7 +25,6 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Radio; using InventorySystem.Items.ThrowableProjectiles; using InventorySystem.Items.ToggleableLights; - using InventorySystem.Items.ToggleableLights.Flashlight; using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp1576; using InventorySystem.Items.Usables.Scp244; @@ -33,7 +32,6 @@ namespace Exiled.API.Features.Items using UnityEngine; using BaseConsumable = InventorySystem.Items.Usables.Consumable; - using Object = UnityEngine.Object; /// /// A wrapper class for . @@ -43,7 +41,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. @@ -219,6 +217,15 @@ public static Item Get(ItemBase itemBase) }; } + /// + /// Gets an existing or creates a new instance of one. + /// + /// The to convert into an item. + /// The specified type. + /// The item wrapper for the given . + public static T Get(ItemBase itemBase) + where T : Item => Get(itemBase) as T; + /// /// Gets the Item belonging to the specified serial. /// @@ -279,6 +286,40 @@ ItemType.KeycardGuard or ItemType.KeycardJanitor or ItemType.KeycardO5 or ItemTy _ => new Item(type), }; + /// + /// Creates a new with the proper inherited subclass. + /// + /// Based on the , the returned can be casted into a subclass to gain more control over the object. + ///
- Usable items (Adrenaline, Medkit, Painkillers, SCP-207, SCP-268, and SCP-500) should be casted to the class. + ///
- All valid ammo should be casted to the class. + ///
- All valid firearms (not including the Micro HID) should be casted to the class. + ///
- All valid keycards should be casted to the class. + ///
- All valid armor should be casted to the class. + ///
- Explosive grenades and SCP-018 should be casted to the class. + ///
- Flash grenades should be casted to the class. + ///
+ /// + ///
The following have their own respective classes: + ///
- Flashlights can be casted to . + ///
- Radios can be casted to . + ///
- The Micro HID can be casted to . + ///
- SCP-244 A and B variants can be casted to . + ///
- SCP-330 can be casted to . + ///
- SCP-2176 can be casted to the class. + ///
- SCP-1576 can be casted to the class. + ///
- Jailbird can be casted to the class. + ///
+ /// + /// Items that are not listed above do not have a subclass, and can only use the base class. + /// + ///
+ /// The of the item to create. + /// The who owns the item by default. + /// The specified type. + /// The created. This can be cast as a subclass. + public static Item Create(ItemType type, Player owner = null) + where T : Item => Create(type, owner) as T; + /// /// Gives this item to a . /// diff --git a/EXILED/Exiled.API/Features/Items/Jailbird.cs b/EXILED/Exiled.API/Features/Items/Jailbird.cs index 9e508da11..2f8bfb524 100644 --- a/EXILED/Exiled.API/Features/Items/Jailbird.cs +++ b/EXILED/Exiled.API/Features/Items/Jailbird.cs @@ -7,11 +7,14 @@ namespace Exiled.API.Features.Items { + using System; + using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; using InventorySystem.Items.Autosync; using InventorySystem.Items.Jailbird; using Mirror; + using UnityEngine; using JailbirdPickup = Pickups.JailbirdPickup; @@ -114,12 +117,35 @@ public JailbirdWearState WearState get => Base._deterioration.WearState; set { - if (JailbirdDeteriorationTracker.ReceivedStates.ContainsKey(Serial)) - JailbirdDeteriorationTracker.ReceivedStates[Serial] = value; + TotalDamageDealt = GetDamage(value); + TotalCharges = GetCharge(value); Base._deterioration.RecheckUsage(); } } + /// + /// Calculates the damage corresponding to a given . + /// + /// The wear state to calculate damage for. + /// The amount of damage associated with the specified wear state. + public float GetDamage(JailbirdWearState wearState) + { + foreach (Keyframe keyframe in Base._deterioration._damageToWearState.keys) + { + if (Base._deterioration.FloatToState(keyframe.value) == wearState) + return keyframe.time; + } + + throw new Exception("Wear state not found in damage to wear state mapping."); + } + + /// + /// Gets the charge needed to reach a specific . + /// + /// The desired wear state to calculate the charge for. + /// The charge value required to achieve the specified wear state. + public int GetCharge(JailbirdWearState wearState) => (int)wearState; + /// /// Breaks the Jailbird. /// diff --git a/EXILED/Exiled.API/Features/Items/Scp018.cs b/EXILED/Exiled.API/Features/Items/Scp018.cs index 57f8122c1..7c08c7c6d 100644 --- a/EXILED/Exiled.API/Features/Items/Scp018.cs +++ b/EXILED/Exiled.API/Features/Items/Scp018.cs @@ -86,7 +86,7 @@ public Scp018Projectile SpawnActive(Vector3 position, Player owner = null) ipb.Info = new PickupSyncInfo(Type, Weight, ItemSerialGenerator.GenerateNext()); - Scp018Projectile grenade = (Scp018Projectile)Pickup.Get(ipb); + Scp018Projectile grenade = Pickup.Get(ipb); grenade.Base.gameObject.SetActive(true); diff --git a/EXILED/Exiled.API/Features/Items/Scp2176.cs b/EXILED/Exiled.API/Features/Items/Scp2176.cs index 412c371d6..0ca9fc54d 100644 --- a/EXILED/Exiled.API/Features/Items/Scp2176.cs +++ b/EXILED/Exiled.API/Features/Items/Scp2176.cs @@ -71,7 +71,7 @@ public Scp2176Projectile SpawnActive(Vector3 position, Player owner = null) ipb.Info = new PickupSyncInfo(Type, Weight, ItemSerialGenerator.GenerateNext()); - Scp2176Projectile grenade = (Scp2176Projectile)Pickup.Get(ipb); + Scp2176Projectile grenade = Pickup.Get(ipb); grenade.Base.gameObject.SetActive(true); diff --git a/EXILED/Exiled.API/Features/Items/Scp330.cs b/EXILED/Exiled.API/Features/Items/Scp330.cs index d0a7e3b9c..689929f12 100644 --- a/EXILED/Exiled.API/Features/Items/Scp330.cs +++ b/EXILED/Exiled.API/Features/Items/Scp330.cs @@ -197,7 +197,7 @@ public IEnumerable DropCandy(CandyKindID type, bool dropAll = fals ipb.Info = new(Type, Weight, ItemSerialGenerator.GenerateNext()); - Scp330Pickup pickup = (Scp330Pickup)Pickup.Get(ipb); + Scp330Pickup pickup = Pickup.Get(ipb); if (exposedType is not CandyKindID.None) pickup.ExposedCandy = exposedType; @@ -218,7 +218,7 @@ public IEnumerable DropCandy(CandyKindID type, bool dropAll = fals ipb.Info = new(Type, Weight, ItemSerialGenerator.GenerateNext()); - Scp330Pickup pickup = (Scp330Pickup)Pickup.Get(ipb); + Scp330Pickup pickup = Pickup.Get(ipb); if (exposedType is not CandyKindID.None) pickup.ExposedCandy = exposedType; diff --git a/EXILED/Exiled.API/Features/Items/Throwable.cs b/EXILED/Exiled.API/Features/Items/Throwable.cs index 4946d1722..4de522295 100644 --- a/EXILED/Exiled.API/Features/Items/Throwable.cs +++ b/EXILED/Exiled.API/Features/Items/Throwable.cs @@ -29,7 +29,7 @@ public Throwable(ThrowableItem itemBase) { Base = itemBase; Base.Projectile.gameObject.SetActive(false); - Projectile = (Projectile)Pickup.Get(Object.Instantiate(Base.Projectile)); + Projectile = Pickup.Get(Object.Instantiate(Base.Projectile)); Base.Projectile.gameObject.SetActive(true); Projectile.Serial = Serial; } diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index 944e5bd77..9a3b183c2 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. @@ -76,7 +76,7 @@ internal Lift(ElevatorChamber elevator) /// /// Gets a value of the internal doors list. /// - public IReadOnlyCollection Doors => internalDoorsList.Select(x => Door.Get(x).As()).ToList(); + public IReadOnlyCollection Doors => internalDoorsList.Select(x => Door.Get(x)).ToList(); /// /// Gets a of in the . @@ -201,7 +201,7 @@ public float AnimationTime /// /// Gets the . /// - public Doors.ElevatorDoor CurrentDestination => Door.Get(Base.CurrentDestination).As(); + public Doors.ElevatorDoor CurrentDestination => Door.Get(Base.CurrentDestination); /// /// Gets a of which contains all the instances from the specified . diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 14f6d8214..3f1f6fc1a 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -28,9 +28,6 @@ namespace Exiled.API.Features using LightContainmentZoneDecontamination; using MapGeneration; using MapGeneration.Distributors; - using Mirror; - using PlayerRoles; - using PlayerRoles.PlayableScps.Scp173; using PlayerRoles.PlayableScps.Scp939; using PlayerRoles.Ragdolls; using RelativePositioning; @@ -39,8 +36,6 @@ namespace Exiled.API.Features using Utils.Networking; using Object = UnityEngine.Object; - using Scp173GameRole = PlayerRoles.PlayableScps.Scp173.Scp173Role; - using Scp939GameRole = PlayerRoles.PlayableScps.Scp939.Scp939Role; /// /// A set of tools to easily handle the in-game map. @@ -57,53 +52,17 @@ public static class Map /// internal static readonly List TeleportsValue = new(8); - /// - /// A list of s on the map. - /// - internal static readonly List ToysValue = new(); - - private static TantrumEnvironmentalHazard tantrumPrefab; - private static Scp939AmnesticCloudInstance amnesticCloudPrefab; - private static AmbientSoundPlayer ambientSoundPlayer; /// /// Gets the tantrum prefab. /// - public static TantrumEnvironmentalHazard TantrumPrefab - { - get - { - if (tantrumPrefab == null) - { - Scp173GameRole scp173Role = (Scp173GameRole)RoleTypeId.Scp173.GetRoleBase(); - - if (scp173Role.SubroutineModule.TryGetSubroutine(out Scp173TantrumAbility scp173TantrumAbility)) - tantrumPrefab = scp173TantrumAbility._tantrumPrefab; - } - - return tantrumPrefab; - } - } + public static TantrumEnvironmentalHazard TantrumPrefab => TantrumHazard.TantrumPrefab; // TODO: Remove this. /// /// Gets the amnestic cloud prefab. /// - public static Scp939AmnesticCloudInstance AmnesticCloudPrefab - { - get - { - if (amnesticCloudPrefab == null) - { - Scp939GameRole scp939Role = (Scp939GameRole)RoleTypeId.Scp939.GetRoleBase(); - - if (scp939Role.SubroutineModule.TryGetSubroutine(out Scp939AmnesticCloudAbility ability)) - amnesticCloudPrefab = ability._instancePrefab; - } - - return amnesticCloudPrefab; - } - } + public static Scp939AmnesticCloudInstance AmnesticCloudPrefab => AmnesticCloudHazard.AmnesticCloudPrefab; // TODO: Remove this. /// /// Gets a value indicating whether decontamination has begun in the light containment zone. @@ -130,7 +89,7 @@ DecontaminationController.Singleton.NetworkDecontaminationOverride is Decontamin /// /// Gets all objects. /// - public static ReadOnlyCollection Toys { get; } = ToysValue.AsReadOnly(); + public static ReadOnlyCollection Toys => AdminToy.BaseToAdminToy.Values.ToList().AsReadOnly(); // TODO: Obsolete it and make people use AdminToy.List /// /// Gets or sets the current seed of the map. @@ -286,21 +245,7 @@ public static void PlayAmbientSound(int id) /// Whether or not the tantrum will apply the effect. /// If is , the tantrum is moved slightly up from its original position. Otherwise, the collision will not be detected and the slowness will not work. /// The instance. - public static TantrumHazard PlaceTantrum(Vector3 position, bool isActive = true) - { - TantrumEnvironmentalHazard tantrum = Object.Instantiate(TantrumPrefab); - - if (!isActive) - tantrum.SynchronizedPosition = new RelativePosition(position); - else - tantrum.SynchronizedPosition = new RelativePosition(position + (Vector3.up * 0.25f)); - - tantrum._destroyed = !isActive; - - NetworkServer.Spawn(tantrum.gameObject); - - return Hazard.Get(tantrum).Cast(); - } + public static TantrumHazard PlaceTantrum(Vector3 position, bool isActive = true) => TantrumHazard.PlaceTantrum(position, isActive); // TODO: Remove this. /// /// Destroy all objects. diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 473bb4c14..6824cbab9 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -17,7 +17,6 @@ namespace Exiled.API.Features using Exiled.API.Enums; using Exiled.API.Extensions; using Exiled.API.Features.Components; - using Footprinting; using MEC; @@ -52,6 +51,20 @@ public Npc(GameObject gameObject) /// public static new List List => Player.List.OfType().ToList(); + /// + /// Gets or sets the player's position. + /// + public override Vector3 Position + { + get => base.Position; + set + { + base.Position = value; + if (Role is Roles.FpcRole fpcRole) + fpcRole.RelativePosition = new(value); + } + } + /// /// Retrieves the NPC associated with the specified ReferenceHub. /// diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index 70db62a8a..6c600dd4c 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -11,6 +11,7 @@ namespace Exiled.API.Features.Pickups using System.Collections.Generic; using System.Linq; + using Exiled.API.Extensions; using Exiled.API.Features.Core; using Exiled.API.Features.Pickups.Projectiles; using Exiled.API.Interfaces; @@ -208,6 +209,11 @@ public float PickupTime /// public ItemType Type => Base.NetworkInfo.ItemId; + /// + /// Gets the of the item. + /// + public ItemCategory Category => Type.GetCategory(); + /// /// Gets or sets a value indicating whether the pickup is locked (can't be picked up). /// @@ -338,6 +344,15 @@ public static Pickup Get(ItemPickupBase pickupBase) }; } + /// + /// Gets an existing or creates a new instance of one. + /// + /// The to convert into an pickup. + /// The specified type. + /// The pickup wrapper for the given . + public static T Get(ItemPickupBase pickupBase) + where T : Pickup => Get(pickupBase) as T; + /// /// Gets the given a . /// @@ -487,6 +502,36 @@ public static IEnumerable Get(IEnumerable gameObjects) _ => new Pickup(type), }; + /// + /// Creates and returns a new with the proper inherited subclass. + /// + /// Based on the , the returned can be cast into a subclass to gain more control over the object. + ///
- All valid ammo should be cast to the class. + ///
- All valid firearms (not including the Micro HID) should be cast to the class. + ///
- All valid keycards should be cast to the class. + ///
- All valid armor should be cast to the class. + ///
- All grenades and throwables (not including SCP-018 and SCP-2176) should be cast to the class. + ///
+ /// + ///
The following have their own respective classes: + ///
- Radios can be cast to . + ///
- The Micro HID can be cast to . + ///
- SCP-244 A and B variants can be cast to . + ///
- SCP-330 can be cast to . + ///
- SCP-018 can be cast to . + ///
- SCP-2176 can be cast to . + ///
+ /// + /// Items that are not listed above do not have a subclass, and can only use the base class. + /// + ///
+ /// The of the pickup. + /// The specified type. + /// The created . + /// + public static Pickup Create(ItemType type) + where T : Pickup => Create(type) as T; + /// /// Creates and spawns a . /// @@ -498,6 +543,19 @@ public static IEnumerable Get(IEnumerable gameObjects) /// public static Pickup CreateAndSpawn(ItemType type, Vector3 position, Quaternion rotation, Player previousOwner = null) => Create(type).Spawn(position, rotation, previousOwner); + /// + /// Creates and spawns a . + /// + /// The of the pickup. + /// The position to spawn the at. + /// The rotation to spawn the . + /// An optional previous owner of the item. + /// The specified type. + /// The . See documentation of for more information on casting. + /// + public static Pickup CreateAndSpawn(ItemType type, Vector3 position, Quaternion rotation, Player previousOwner = null) + where T : Pickup => CreateAndSpawn(type, position, rotation, previousOwner) as T; + /// /// Spawns a . /// diff --git a/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs b/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs index cd18059bc..5406d6414 100644 --- a/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs +++ b/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs @@ -83,17 +83,37 @@ internal Projectile(ItemType type) /// Projectile that are not listed will cause an Exception. /// ///
- /// The of the pickup. - /// The created . + /// The of the projectile. + /// The created . public static Projectile Create(ProjectileType projectiletype) => projectiletype switch { ProjectileType.Scp018 => new Scp018Projectile(), ProjectileType.Flashbang => new FlashbangProjectile(), ProjectileType.Scp2176 => new Scp2176Projectile(), ProjectileType.FragGrenade => new ExplosionGrenadeProjectile(ItemType.GrenadeHE), - _ => throw new System.Exception($"ProjectileType does not contain a valid value : {projectiletype}"), + _ => throw new Exception($"ProjectileType does not contain a valid value : {projectiletype}"), }; + /// + /// Creates and returns a new with the proper inherited subclass. + /// + /// Based on the , the returned can be casted into a subclass to gain more control over the object. + ///
The following have their own respective classes: + ///
- FragGrenade can be casted to . + ///
- Flashbang can be casted to . + ///
- Scp018 A and B variants can be casted to . + ///
- Scp2176 can be casted to . + ///
+ /// + /// Projectile that are not listed will cause an Exception. + /// + ///
+ /// The of the projectile. + /// The specified type. + /// The created . + public static Projectile Create(ProjectileType projectiletype) + where T : Projectile => Create(projectiletype) as T; + /// /// Spawns a . /// @@ -110,14 +130,27 @@ public static Projectile Spawn(Projectile pickup, Vector3 position, Quaternion r /// /// Creates and spawns a . /// - /// The of the pickup. + /// The of the projectile. /// The position to spawn the at. /// The rotation to spawn the . /// Whether the should be in active state after spawn. /// An optional previous owner of the item. - /// The . See documentation of for more information on casting. + /// The . See documentation of for more information on casting. public static Projectile CreateAndSpawn(ProjectileType type, Vector3 position, Quaternion rotation, bool shouldBeActive = true, Player previousOwner = null) => Create(type).Spawn(position, rotation, shouldBeActive, previousOwner); + /// + /// Creates and spawns a . + /// + /// The of the projectile. + /// The position to spawn the at. + /// The rotation to spawn the . + /// Whether the should be in active state after spawn. + /// An optional previous owner of the item. + /// The specified type. + /// The . See documentation of for more information on casting. + public static Projectile CreateAndSpawn(ProjectileType type, Vector3 position, Quaternion rotation, bool shouldBeActive = true, Player previousOwner = null) + where T : Projectile => CreateAndSpawn(type, position, rotation, shouldBeActive, previousOwner) as T; + /// /// Activates the current . /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 4991e13d4..0885000f7 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(); @@ -282,6 +292,7 @@ public AuthenticationType AuthenticationType "northwood" => AuthenticationType.Northwood, "localhost" => AuthenticationType.LocalHost, "ID_Dedicated" => AuthenticationType.DedicatedServer, + "offline" => AuthenticationType.Offline, _ => AuthenticationType.Unknown, }; } @@ -503,7 +514,7 @@ public Player Cuffer ///
/// /// - public Vector3 Position + public virtual Vector3 Position { get => Transform.position; set => ReferenceHub.TryOverridePosition(value, Vector3.zero); @@ -958,7 +969,7 @@ public Item CurrentItem /// /// Gets the armor that the player is currently wearing. Value will be if the player is not wearing any armor. /// - public Armor CurrentArmor => Inventory.TryGetBodyArmor(out BodyArmor armor) ? (Armor)Item.Get(armor) : null; + public Armor CurrentArmor => Inventory.TryGetBodyArmor(out BodyArmor armor) ? Item.Get(armor) : null; /// /// Gets the class. @@ -1266,8 +1277,13 @@ public static Player Get(GameObject gameObject) if (Dictionary.TryGetValue(gameObject, out Player player)) return player; - UnverifiedPlayers.TryGetValue(gameObject, out player); - return player; + if (UnverifiedPlayers.TryGetValue(gameObject, out player)) + return player; + + if (ReferenceHub.TryGetHub(gameObject, out ReferenceHub hub)) + return new(hub); + + return null; } /// @@ -1295,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) { @@ -2319,6 +2335,16 @@ public void Broadcast(ushort duration, string message, global::Broadcast.Broadca public void AddAmmo(AmmoType ammoType, ushort amount) => Inventory.ServerAddAmmo(ammoType.GetItemType(), amount); + /// + /// Adds the amount of a specified ammo type to player's inventory. + /// + /// A dictionary of ammo types that will be added. + public void AddAmmo(Dictionary ammoBag) + { + foreach (KeyValuePair kvp in ammoBag) + AddAmmo(kvp.Key, kvp.Value); + } + /// /// Adds the amount of a weapon's ammo type to the player's inventory. /// @@ -2338,6 +2364,16 @@ public void SetAmmo(AmmoType ammoType, ushort amount) Inventory.ServerSetAmmo(itemType, amount); } + /// + /// Sets the amount of a specified ammo type to player's inventory. + /// + /// A dictionary of ammo types that will be added. + public void SetAmmo(Dictionary ammoBag) + { + foreach (KeyValuePair kvp in ammoBag) + SetAmmo(kvp.Key, kvp.Value); + } + /// /// Gets the ammo count of a specified ammo type in a player's inventory. /// @@ -2357,21 +2393,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. @@ -2582,7 +2767,7 @@ public void AddItem(Firearm item, IEnumerable identifiers) /// The that was added. public Item AddItem(FirearmPickup pickup, IEnumerable identifiers) { - Firearm firearm = (Firearm)Item.Get(Inventory.ServerAddItem(pickup.Type, pickup.Serial, pickup.Base)); + Firearm firearm = Item.Get(Inventory.ServerAddItem(pickup.Type, pickup.Serial, pickup.Base)); if (identifiers is not null) firearm.AddAttachment(identifiers); @@ -2728,8 +2913,8 @@ public void ResetInventory(IEnumerable newItems) /// public void ClearInventory(bool destroy = true) { - ClearItems(destroy); ClearAmmo(); + ClearItems(destroy); } /// @@ -3221,7 +3406,7 @@ public void ChangeEffectIntensity(string effectName, byte intensity, float durat /// Whether or not the tantrum will apply the effect. /// If is , the tantrum is moved slightly up from its original position. Otherwise, the collision will not be detected and the slowness will not work. /// The instance.. - public TantrumHazard PlaceTantrum(bool isActive = true) => Map.PlaceTantrum(Position, isActive); + public TantrumHazard PlaceTantrum(bool isActive = true) => TantrumHazard.PlaceTantrum(Position, isActive); /// /// Gives a new to the player. diff --git a/EXILED/Exiled.API/Features/PrefabHelper.cs b/EXILED/Exiled.API/Features/PrefabHelper.cs index de97e15d5..fd18e9fe9 100644 --- a/EXILED/Exiled.API/Features/PrefabHelper.cs +++ b/EXILED/Exiled.API/Features/PrefabHelper.cs @@ -41,6 +41,21 @@ public static PrefabAttribute GetPrefabAttribute(this PrefabType prefabType) return type.GetField(Enum.GetName(type, prefabType)).GetCustomAttribute(); } + /// + /// Gets the prefab of the specified . + /// + /// The to get prefab of. + /// The to get. + /// Returns the prefab component as {T}. + public static T GetPrefab(PrefabType type) + where T : Component + { + if (!Stored.TryGetValue(type, out GameObject gameObject) || !gameObject.TryGetComponent(out T component)) + return null; + + return component; + } + /// /// Spawns a prefab on server. /// @@ -68,9 +83,7 @@ public static GameObject Spawn(PrefabType prefabType, Vector3 position = default public static T Spawn(PrefabType prefabType, Vector3 position = default, Quaternion rotation = default) where T : Component { - if (!Stored.TryGetValue(prefabType, out GameObject gameObject) || !gameObject.TryGetComponent(out T component)) - return null; - T obj = UnityEngine.Object.Instantiate(component, position, rotation); + T obj = UnityEngine.Object.Instantiate(GetPrefab(prefabType), position, rotation); NetworkServer.Spawn(obj.gameObject); return obj; } 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..4ca71d8c8 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; /// @@ -47,14 +50,36 @@ protected FpcRole(FpcStandardRoleBase baseRole) public FpcStandardRoleBase FirstPersonController { get; } /// - /// Gets or sets the player's relative position. + /// Gets or sets the player's relative position as perceived by the server. /// public RelativePosition RelativePosition + { + get => new(Owner.Position); + set => Owner.Position = value.Position; + } + + /// + /// Gets or sets the player's relative position as perceived by the client. + /// + public RelativePosition ClientRelativePosition { get => FirstPersonController.FpcModule.Motor.ReceivedPosition; 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/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 26cf64c98..b1b05e8c1 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -111,6 +111,15 @@ public static string Name /// public static double Tps => Math.Round(1f / Time.smoothDeltaTime); + /// + /// Gets or sets the max ticks per second of the server. + /// + public static short MaxTps + { + get => ServerStatic.ServerTickrate; + set => ServerStatic.ServerTickrate = value; + } + /// /// Gets the actual frametime of the server. /// @@ -178,6 +187,11 @@ public static bool IsWhitelisted set => ServerConsole.WhiteListEnabled = value; } + /// + /// Gets the list of user IDs of players currently whitelisted. + /// + public static HashSet WhitelistedPlayers => WhiteList.Users; + /// /// Gets a value indicating whether or not this server is verified. /// diff --git a/EXILED/Exiled.API/Features/TeslaGate.cs b/EXILED/Exiled.API/Features/TeslaGate.cs index 7ebdf6480..4d7909592 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. @@ -178,7 +178,7 @@ public bool UseInstantBurst /// /// Gets a of which contains all the tantrums to destroy. /// - public IEnumerable TantrumsToDestroy => Base.TantrumsToBeDestroyed.Select(x => Hazard.Get(x) as TantrumHazard); + public IEnumerable TantrumsToDestroy => Base.TantrumsToBeDestroyed.Select(x => Hazard.Get(x)); /// /// Gets a of which contains all the players inside the hurt range. diff --git a/EXILED/Exiled.API/Features/Toys/AdminToy.cs b/EXILED/Exiled.API/Features/Toys/AdminToy.cs index fdbe03da1..5287096a1 100644 --- a/EXILED/Exiled.API/Features/Toys/AdminToy.cs +++ b/EXILED/Exiled.API/Features/Toys/AdminToy.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features.Toys { + using System.Collections.Generic; using System.Linq; using AdminToys; @@ -14,6 +15,7 @@ namespace Exiled.API.Features.Toys using Enums; using Exiled.API.Interfaces; using Footprinting; + using InventorySystem.Items; using Mirror; using UnityEngine; @@ -23,6 +25,11 @@ namespace Exiled.API.Features.Toys /// public abstract class AdminToy : IWorldSpace { + /// + /// A dictionary of all 's that have been converted into . + /// + internal static readonly Dictionary BaseToAdminToy = new(new ComponentsEqualityComparer()); + /// /// Initializes a new instance of the class. /// @@ -33,9 +40,14 @@ internal AdminToy(AdminToyBase toyAdminToyBase, AdminToyType type) AdminToyBase = toyAdminToyBase; ToyType = type; - Map.ToysValue.Add(this); + BaseToAdminToy.Add(toyAdminToyBase, this); } + /// + /// Gets a list of all 's on the server. + /// + public static IReadOnlyCollection List => BaseToAdminToy.Values; + /// /// Gets the original . /// @@ -130,7 +142,31 @@ public bool IsStatic /// /// The instance. /// The corresponding instance. - public static AdminToy Get(AdminToyBase adminToyBase) => Map.Toys.FirstOrDefault(x => x.AdminToyBase == adminToyBase); + public static AdminToy Get(AdminToyBase adminToyBase) + { + if (adminToyBase == null) + return null; + + if (BaseToAdminToy.TryGetValue(adminToyBase, out AdminToy adminToy)) + return adminToy; + + return adminToyBase switch + { + LightSourceToy lightSourceToy => new Light(lightSourceToy), + PrimitiveObjectToy primitiveObjectToy => new Primitive(primitiveObjectToy), + ShootingTarget shootingTarget => new ShootingTargetToy(shootingTarget), + _ => throw new System.NotImplementedException() + }; + } + + /// + /// Gets the by . + /// + /// The to convert into an admintoy. + /// The specified type. + /// The admintoy wrapper for the given . + public static T Get(AdminToyBase adminToyBase) + where T : AdminToy => Get(adminToyBase) as T; /// /// Spawns the toy into the game. Use to remove it. @@ -147,7 +183,7 @@ public bool IsStatic /// public void Destroy() { - Map.ToysValue.Remove(this); + BaseToAdminToy.Remove(AdminToyBase); NetworkServer.Destroy(AdminToyBase.gameObject); } } 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.CreditTags/Features/DatabaseHandler.cs b/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs index d052b914c..e31fe2615 100644 --- a/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs +++ b/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Exiled Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -17,7 +17,7 @@ namespace Exiled.CreditTags.Features public static class DatabaseHandler { - private const string Url = "https://raw.githubusercontent.com/Exiled-Official/CreditTags/main/data.yml"; + private const string Url = "https://raw.githubusercontent.com/ExMod-Team/CreditTags/main/data.yml"; private const string ETagCacheFileName = "etag_cache.txt"; private const string DatabaseCacheFileName = "data.yml"; private const int CacheTimeInMinutes = 5; diff --git a/EXILED/Exiled.CustomItems/Patches/PlayerInventorySee.cs b/EXILED/Exiled.CustomItems/Patches/PlayerInventorySee.cs index 8f35712fb..1620477ba 100644 --- a/EXILED/Exiled.CustomItems/Patches/PlayerInventorySee.cs +++ b/EXILED/Exiled.CustomItems/Patches/PlayerInventorySee.cs @@ -8,6 +8,7 @@ namespace Exiled.CustomItems.Patches { using System.Collections.Generic; + using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -52,7 +53,7 @@ private static IEnumerable Transpiler(IEnumerable !x.IsGenericMethod && x.Name is nameof(Item.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemBase))), new(OpCodes.Dup), new(OpCodes.Stloc_S, item.LocalIndex), new(OpCodes.Brfalse_S, continueLabel), 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) diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Events/Commands/TpsCommand.cs new file mode 100644 index 000000000..2d7412919 --- /dev/null +++ b/EXILED/Exiled.Events/Commands/TpsCommand.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Commands +{ + using System; + + using CommandSystem; + using Exiled.API.Features; + + /// + /// Command for showing current server TPS. + /// + [CommandHandler(typeof(RemoteAdminCommandHandler))] + [CommandHandler(typeof(GameConsoleCommandHandler))] + public class TpsCommand : ICommand + { + /// + public string Command { get; } = "tps"; + + /// + public string[] Aliases { get; } = Array.Empty(); + + /// + public string Description { get; } = "Shows the current TPS of the server"; + + /// + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + double diff = Server.Tps / Server.MaxTps; + string color = diff switch + { + > 0.9 => "green", + > 0.5 => "yellow", + _ => "red" + }; + + response = $"{Server.Tps}/{Server.MaxTps}"; + return true; + } + } +} \ No newline at end of file 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/Item/ChangingAttachmentsEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/ChangingAttachmentsEventArgs.cs index 0e1c8fbed..fad3ec0b5 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/ChangingAttachmentsEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/ChangingAttachmentsEventArgs.cs @@ -38,17 +38,13 @@ public class ChangingAttachmentsEventArgs : IPlayerEvent, IDeniableEvent, IFirea /// /// /// - public ChangingAttachmentsEventArgs( - Player player, - Firearm firearm, - uint code, - bool isAllowed = true) + public ChangingAttachmentsEventArgs(Player player, InventorySystem.Items.Firearms.Firearm firearm, uint code, bool isAllowed = true) { Player = player; - Firearm = firearm; - CurrentAttachmentIdentifiers = firearm.AttachmentIdentifiers; - NewAttachmentIdentifiers = firearm.FirearmType.GetAttachmentIdentifiers(code).ToList(); - CurrentCode = firearm.Base.GetCurrentAttachmentsCode(); + Firearm = Item.Get(firearm); + CurrentAttachmentIdentifiers = Firearm.AttachmentIdentifiers; + NewAttachmentIdentifiers = Firearm.FirearmType.GetAttachmentIdentifiers(code).ToList(); + CurrentCode = firearm.GetCurrentAttachmentsCode(); NewCode = code; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs index 1b17a1bb0..6ac613800 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs @@ -7,6 +7,8 @@ namespace Exiled.Events.EventArgs.Item { + using System; + using Exiled.API.Features; using Exiled.API.Features.Items; using Exiled.Events.EventArgs.Interfaces; @@ -25,8 +27,10 @@ public class ChargingJailbirdEventArgs : IPlayerEvent, IItemEvent, IDeniableEven public ChargingJailbirdEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase swingItem, bool isAllowed = true) { Player = Player.Get(player); - Item = Item.Get(swingItem); + Jailbird = (Jailbird)Item.Get(swingItem); +#pragma warning disable CS0618 IsAllowed = isAllowed; +#pragma warning restore CS0618 } /// @@ -35,13 +39,23 @@ public ChargingJailbirdEventArgs(ReferenceHub player, InventorySystem.Items.Item public Player Player { get; } /// - /// Gets the that is being charged. This will always be a . + /// Gets the that is being charged. + /// + public Jailbird Jailbird { get; } + + /// + /// Gets the that is being charged. /// - public Item Item { get; } + public Item Item => Jailbird; /// /// Gets or sets a value indicating whether or not the Jailbird can be charged. /// - public bool IsAllowed { get; set; } + public bool IsAllowed + { + get; + [Obsolete("This event cannot be denied as it will cause desync.")] + set; + } } } diff --git a/EXILED/Exiled.Events/EventArgs/Item/SwingingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/SwingingEventArgs.cs index 17c0319b0..6c8f0fce8 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/SwingingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/SwingingEventArgs.cs @@ -25,7 +25,7 @@ public class SwingingEventArgs : IPlayerEvent, IItemEvent, IDeniableEvent public SwingingEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase swingItem, bool isAllowed = true) { Player = Player.Get(player); - Item = Item.Get(swingItem); + Jailbird = (Jailbird)Item.Get(swingItem); IsAllowed = isAllowed; } @@ -34,10 +34,15 @@ public SwingingEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase swi /// public Player Player { get; } + /// + /// Gets the that is being swung. + /// + public Jailbird Jailbird { get; } + /// /// Gets the that is being swung. /// - public Item Item { get; } + public Item Item => Jailbird; /// /// Gets or sets a value indicating whether or not the item can be swung. diff --git a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs index 28b7b30f0..a0dbd7920 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs @@ -37,7 +37,7 @@ public class ExplodingGrenadeEventArgs : IPlayerEvent, IDeniableEvent public ExplodingGrenadeEventArgs(Footprint thrower, Vector3 position, ExplosionGrenade grenade, Collider[] targets) { Player = Player.Get(thrower.Hub); - Projectile = (EffectGrenadeProjectile)Pickup.Get(grenade); + Projectile = Pickup.Get(grenade); Position = position; TargetsToAffect = ListPool.Pool.Get(); @@ -97,7 +97,7 @@ public ExplodingGrenadeEventArgs(Footprint thrower, Vector3 position, ExplosionG public ExplodingGrenadeEventArgs(Player thrower, EffectGrenade grenade, List targetsToAffect, bool isAllowed = true) { Player = thrower ?? Server.Host; - Projectile = (EffectGrenadeProjectile)Pickup.Get(grenade); + Projectile = Pickup.Get(grenade); Position = Projectile.Position; TargetsToAffect = ListPool.Pool.Get(targetsToAffect ?? new()); IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Map/Scp244SpawningEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/Scp244SpawningEventArgs.cs index 1ed101269..fd6a6fa76 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/Scp244SpawningEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/Scp244SpawningEventArgs.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Exiled Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -10,6 +10,8 @@ namespace Exiled.Events.EventArgs.Map using API.Features; using Exiled.API.Features.Pickups; using Interfaces; + using InventorySystem.Items.Usables.Scp244; + using MapGeneration; /// /// Contains all information up to spawning Scp244. @@ -25,11 +27,10 @@ public class Scp244SpawningEventArgs : IRoomEvent, IPickupEvent, IDeniableEvent /// /// /// - public Scp244SpawningEventArgs(Room room, Pickup scp244Pickup) + public Scp244SpawningEventArgs(RoomIdentifier room, Scp244DeployablePickup scp244Pickup) { - Room = room; - Pickup = scp244Pickup; - Scp244Pickup = scp244Pickup.As(); + Room = Room.Get(room); + Scp244Pickup = Pickup.Get(scp244Pickup); } /// @@ -38,7 +39,7 @@ public Scp244SpawningEventArgs(Room room, Pickup scp244Pickup) public Room Room { get; } /// - public Pickup Pickup { get; } + public Pickup Pickup => Scp244Pickup; /// /// Gets a value indicating the pickup being spawning. diff --git a/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs index ac07393e7..f3f623f40 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs @@ -27,7 +27,7 @@ public class CancellingItemUseEventArgs : IPlayerEvent, IDeniableEvent, IUsableE public CancellingItemUseEventArgs(Player player, UsableItem item) { Player = player; - Usable = Item.Get(item) is Usable usable ? usable : null; + Usable = Item.Get(item); } /// 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/EventArgs/Player/ChangingMicroHIDStateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingMicroHIDStateEventArgs.cs index 4cb941b0a..2b9bacdbf 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingMicroHIDStateEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingMicroHIDStateEventArgs.cs @@ -40,7 +40,7 @@ public class ChangingMicroHIDStateEventArgs : IPlayerEvent, IDeniableEvent public ChangingMicroHIDStateEventArgs(Player player, MicroHIDItem microHID, HidState oldState, HidState newState, bool isAllowed = true) { Player = player; - MicroHID = (MicroHid)Item.Get(microHID); + MicroHID = Item.Get(microHID); OldState = oldState; NewState = newState; IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingRadioPresetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingRadioPresetEventArgs.cs index 7b59f2fb9..70af9b581 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingRadioPresetEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingRadioPresetEventArgs.cs @@ -44,7 +44,7 @@ public class ChangingRadioPresetEventArgs : IPlayerEvent, IItemEvent, IDeniableE public ChangingRadioPresetEventArgs(Player player, RadioItem item, RadioRangeLevel oldValue, RadioRangeLevel newValue, bool isAllowed = true) { Player = player; - Radio = (Radio)Item.Get(item); + Radio = Item.Get(item); OldValue = (RadioRange)oldValue; NewValue = (RadioRange)newValue; IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs index 552d70a73..c8ab4d9ba 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs @@ -11,11 +11,10 @@ namespace Exiled.Events.EventArgs.Player using API.Enums; using API.Features; + using Exiled.API.Extensions; using Exiled.API.Features.Pools; using Interfaces; - - using InventorySystem.Configs; - + using InventorySystem; using PlayerRoles; /// @@ -70,17 +69,16 @@ public RoleTypeId NewRole get => newRole; set { - if (StartingInventories.DefinedInventories.ContainsKey(value)) - { - Items.Clear(); - Ammo.Clear(); + InventoryRoleInfo inventory = value.GetInventory(); + + Items.Clear(); + Ammo.Clear(); - foreach (ItemType itemType in StartingInventories.DefinedInventories[value].Items) - Items.Add(itemType); + foreach (ItemType itemType in inventory.Items) + Items.Add(itemType); - foreach (KeyValuePair ammoPair in StartingInventories.DefinedInventories[value].Ammo) - Ammo.Add(ammoPair.Key, ammoPair.Value); - } + foreach (KeyValuePair ammoPair in inventory.Ammo) + Ammo.Add(ammoPair.Key, ammoPair.Value); newRole = value; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/DroppedItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DroppedItemEventArgs.cs index 14d298c84..0109a4bba 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DroppedItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DroppedItemEventArgs.cs @@ -10,6 +10,7 @@ namespace Exiled.Events.EventArgs.Player using API.Features; using Exiled.API.Features.Pickups; using Interfaces; + using InventorySystem.Items.Pickups; /// /// Contains all information after a player drops an item. @@ -28,10 +29,10 @@ public class DroppedItemEventArgs : IPlayerEvent, IPickupEvent /// /// /// - public DroppedItemEventArgs(Player player, Pickup pickup, bool wasThrown) + public DroppedItemEventArgs(Player player, ItemPickupBase pickup, bool wasThrown) { Player = player; - Pickup = pickup; + Pickup = Pickup.Get(pickup); WasThrown = wasThrown; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/LocalReportingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/LocalReportingEventArgs.cs index 316be5039..97a443ea7 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/LocalReportingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/LocalReportingEventArgs.cs @@ -5,7 +5,7 @@ // // ----------------------------------------------------------------------- -namespace Exiled.Events.EventArgs.Player +namespace Exiled.Events.EventArgs.Player // TODO: Wrong namespace should be Server { using API.Features; diff --git a/EXILED/Exiled.Events/EventArgs/Player/RemovedHandcuffsEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RemovedHandcuffsEventArgs.cs new file mode 100644 index 000000000..ee152fb34 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/RemovedHandcuffsEventArgs.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using API.Enums; + using API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information after freeing a handcuffed player. + /// + public class RemovedHandcuffsEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The cuffer player. + /// The target player was uncuffed. + /// The reason for removing the handcuffs. + public RemovedHandcuffsEventArgs(Player cuffer, Player target, UncuffReason uncuffReason) + { + Player = cuffer; + Target = target; + UncuffReason = uncuffReason; + } + + /// + /// Gets the target player to be cuffed. + /// + public Player Target { get; } + + /// + /// Gets the cuffer player. + /// + public Player Player { get; } + + /// + /// Gets the reason for removing handcuffs. + /// + public UncuffReason UncuffReason { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/RemovingHandcuffsEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RemovingHandcuffsEventArgs.cs index c6a90a835..c375bb4b7 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/RemovingHandcuffsEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/RemovingHandcuffsEventArgs.cs @@ -7,6 +7,7 @@ namespace Exiled.Events.EventArgs.Player { + using API.Enums; using API.Features; using Exiled.Events.EventArgs.Interfaces; @@ -20,11 +21,13 @@ public class RemovingHandcuffsEventArgs : IPlayerEvent, IDeniableEvent /// /// The cuffer player. /// The target player to be uncuffed. + /// The reason of removing handcuffs. /// Indicates whether the event can be executed or not. - public RemovingHandcuffsEventArgs(Player cuffer, Player target, bool isAllowed = true) + public RemovingHandcuffsEventArgs(Player cuffer, Player target, UncuffReason uncuffReason, bool isAllowed = true) { Player = cuffer; Target = target; + UncuffReason = uncuffReason; IsAllowed = isAllowed; } @@ -34,13 +37,19 @@ public RemovingHandcuffsEventArgs(Player cuffer, Player target, bool isAllowed = public Player Target { get; } /// - /// Gets or sets a value indicating whether or not the player can be handcuffed. + /// Gets or sets a value indicating whether or not the player can be handcuffed. Denying the event will only have an effect when is until next major update. /// + /// TODO: Update docs and patches public bool IsAllowed { get; set; } /// /// Gets the cuffer player. /// public Player Player { get; } + + /// + /// Gets the reason of removing handcuffs. + /// + public UncuffReason UncuffReason { get; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/ThrowingRequestEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ThrowingRequestEventArgs.cs index 273763fda..089695333 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ThrowingRequestEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ThrowingRequestEventArgs.cs @@ -28,7 +28,7 @@ public class ThrowingRequestEventArgs : IPlayerEvent, IItemEvent public ThrowingRequestEventArgs(Player player, ThrowableItem item, ThrowableNetworkHandler.RequestType request) { Player = player; - Throwable = (Throwable)Item.Get(item); + Throwable = Item.Get(item); RequestType = (ThrowRequest)request; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/ThrownProjectileEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ThrownProjectileEventArgs.cs index aaf654d42..26984b839 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ThrownProjectileEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ThrownProjectileEventArgs.cs @@ -29,8 +29,8 @@ public class ThrownProjectileEventArgs : IPlayerEvent, IItemEvent, IPickupEvent public ThrownProjectileEventArgs(ThrownProjectile projectile, Player player, ThrowableItem item) { Player = player; - Throwable = (Throwable)Item.Get(item); - Projectile = (Projectile)Pickup.Get(projectile); + Throwable = Item.Get(item); + Projectile = Pickup.Get(projectile); } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/TogglingFlashlightEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/TogglingFlashlightEventArgs.cs index b8990f85d..3a87a66a1 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/TogglingFlashlightEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/TogglingFlashlightEventArgs.cs @@ -35,7 +35,7 @@ public class TogglingFlashlightEventArgs : IPlayerEvent, IDeniableEvent, IItemEv public TogglingFlashlightEventArgs(ReferenceHub hub, ToggleableLightItemBase flashlight, bool newState) { Player = Player.Get(hub); - Flashlight = (Flashlight)Item.Get(flashlight); + Flashlight = Item.Get(flashlight); initialState = newState; NewState = newState; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/TogglingRadioEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/TogglingRadioEventArgs.cs index 67db54505..38d0fe09b 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/TogglingRadioEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/TogglingRadioEventArgs.cs @@ -36,7 +36,7 @@ public class TogglingRadioEventArgs : IPlayerEvent, IDeniableEvent, IItemEvent public TogglingRadioEventArgs(Player player, RadioItem radio, bool newState, bool isAllowed = true) { Player = player; - Radio = (Radio)Item.Get(radio); + Radio = Item.Get(radio); NewState = newState; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/UsingMicroHIDEnergyEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UsingMicroHIDEnergyEventArgs.cs index d98f014ed..402d20e1b 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UsingMicroHIDEnergyEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UsingMicroHIDEnergyEventArgs.cs @@ -40,7 +40,7 @@ public class UsingMicroHIDEnergyEventArgs : IPlayerEvent, IDeniableEvent, IItemE public UsingMicroHIDEnergyEventArgs(Player player, MicroHIDItem microHIDitem, HidState currentState, float drain, bool isAllowed = true) { Player = player; - MicroHID = (MicroHid)Item.Get(microHIDitem); + MicroHID = Item.Get(microHIDitem); CurrentState = currentState; Drain = drain; IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs index 90369883f..1f07dadde 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs @@ -36,7 +36,7 @@ public class UsingRadioBatteryEventArgs : IPlayerEvent, IDeniableEvent, IItemEve /// public UsingRadioBatteryEventArgs(RadioItem radio, Player player, float drain, bool isAllowed = true) { - Radio = (Radio)Item.Get(radio); + Radio = Item.Get(radio); Player = player; Drain = drain; IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Scp049/FinishingRecallEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp049/FinishingRecallEventArgs.cs index e2c2c0728..e897dbb04 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp049/FinishingRecallEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp049/FinishingRecallEventArgs.cs @@ -38,7 +38,7 @@ public FinishingRecallEventArgs(Player target, Player scp049, BasicRagdoll ragdo Scp049 = Player.Role.As(); Target = target; Ragdoll = Ragdoll.Get(ragdoll); - IsAllowed = isAllowed && Target.Role is SpectatorRole spectatorRole && spectatorRole.IsReadyToRespawn; + IsAllowed = isAllowed; } /// 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/EventArgs/Scp096/StartPryingGateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp096/StartPryingGateEventArgs.cs index f40e1e874..be8cef0ce 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp096/StartPryingGateEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp096/StartPryingGateEventArgs.cs @@ -36,7 +36,7 @@ public StartPryingGateEventArgs(Player player, PryableDoor gate, bool isAllowed { Player = player; Scp096 = player.Role.As(); - Gate = Door.Get(gate).As(); + Gate = Door.Get(gate); IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Scp244/UsingScp244EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp244/UsingScp244EventArgs.cs index c3077777c..5dca87a99 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp244/UsingScp244EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp244/UsingScp244EventArgs.cs @@ -33,7 +33,7 @@ public class UsingScp244EventArgs : IPlayerEvent, IDeniableEvent /// public UsingScp244EventArgs(Scp244Item scp244, Player player, bool isAllowed = true) { - Scp244 = (Scp244)Item.Get(scp244); + Scp244 = Item.Get(scp244); Player = player; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs index 5aabbd226..985e90337 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. @@ -34,14 +34,17 @@ public class DroppingScp330EventArgs : IPlayerEvent, IDeniableEvent public DroppingScp330EventArgs(Player player, Scp330Bag scp330, CandyKindID candy) { Player = player; - Scp330 = (Scp330)Item.Get(scp330); + Scp330 = Item.Get(scp330); Candy = candy; } /// - /// 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/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/EventArgs/Server/RespawnedTeamEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/RespawnedTeamEventArgs.cs new file mode 100644 index 000000000..4aaa2b363 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Server/RespawnedTeamEventArgs.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Server +{ + using System.Collections.Generic; + using System.Linq; + + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + using Respawning; + + /// + /// Contains all information after team spawns. + /// + public class RespawnedTeamEventArgs : IExiledEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public RespawnedTeamEventArgs(SpawnableTeamType team, IEnumerable hubs) + { + Players = hubs.Select(Player.Get); + Team = team; + } + + /// + /// Gets the list of spawned players. + /// + public IEnumerable Players { get; } + + /// + /// Gets the spawned team. + /// + public SpawnableTeamType Team { get; } + } +} diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 178071bb7..4c5fcabc5 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -21,6 +21,7 @@ namespace Exiled.Events using PlayerRoles.Ragdolls; using PlayerRoles.RoleAssign; using PluginAPI.Events; + using Respawning; using UnityEngine.SceneManagement; /// @@ -70,7 +71,7 @@ public override void OnEnabled() Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; CharacterClassManager.OnRoundStarted += Handlers.Server.OnRoundStarted; - + RespawnManager.ServerOnRespawned += Handlers.Server.OnRespawnedTeam; InventorySystem.InventoryExtensions.OnItemAdded += Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved += Handlers.Player.OnItemRemoved; @@ -105,7 +106,7 @@ public override void OnDisabled() InventorySystem.InventoryExtensions.OnItemAdded -= Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved -= Handlers.Player.OnItemRemoved; - + RespawnManager.ServerOnRespawned -= Handlers.Server.OnRespawnedTeam; RagdollManager.OnRagdollSpawned -= Handlers.Internal.RagdollList.OnSpawnedRagdoll; RagdollManager.OnRagdollRemoved -= Handlers.Internal.RagdollList.OnRemovedRagdoll; ItemPickupBase.OnPickupAdded -= Handlers.Internal.PickupEvent.OnSpawnedPickup; diff --git a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs index 1b1dadf46..f75f2e91f 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs @@ -48,7 +48,7 @@ public static void OnMapGenerated() Map.ClearCache(); PrefabHelper.LoadPrefabs(); - // TODO: Fix For (https://trello.com/c/cUwpZDLs/5003-config-teamrespawnqueue-in-configgameplay-is-not-working-as-expected) + // TODO: Fix For (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/377) PlayerRoles.RoleAssign.HumanSpawner.Handlers[PlayerRoles.Team.ChaosInsurgency] = new PlayerRoles.RoleAssign.OneRoleHumanSpawner(PlayerRoles.RoleTypeId.ChaosConscript); PlayerRoles.RoleAssign.HumanSpawner.Handlers[PlayerRoles.Team.OtherAlive] = new PlayerRoles.RoleAssign.OneRoleHumanSpawner(PlayerRoles.RoleTypeId.Tutorial); PlayerRoles.RoleAssign.HumanSpawner.Handlers[PlayerRoles.Team.Dead] = new PlayerRoles.RoleAssign.OneRoleHumanSpawner(PlayerRoles.RoleTypeId.Spectator); diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 400eb044c..8f9bd58b2 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -86,7 +86,7 @@ public static void OnVerified(VerifiedEventArgs ev) { RoleAssigner.CheckLateJoin(ev.Player.ReferenceHub, ClientInstanceMode.ReadyClient); - // TODO: Remove if this has been fixed for https://trello.com/c/CzPD304L/5983-networking-blackout-is-not-synchronized-for-the-new-players + // TODO: Remove if this has been fixed for https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/52 foreach (Room room in Room.List.Where(current => current.AreLightsOff)) { ev.Player.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkLightsEnabled), true); diff --git a/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs b/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs index 9bcb75cc8..4ecdc5f93 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs @@ -8,7 +8,7 @@ namespace Exiled.Events.Handlers.Internal { using API.Features; - + using Exiled.API.Features.Toys; using UnityEngine.SceneManagement; #pragma warning disable SA1611 // Element parameters should be documented @@ -35,7 +35,7 @@ public static void OnSceneUnloaded(Scene _) { Player.UserIdsCache.Clear(); Player.Dictionary.Clear(); - Map.ToysValue.Clear(); + AdminToy.BaseToAdminToy.Clear(); } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index aca07ce6e..016bdf644 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -223,6 +223,11 @@ public class Player /// public static Event RemovingHandcuffs { get; set; } = new(); + /// + /// Invoked after freeing a handcuffed . + /// + public static Event RemovedHandcuffs { get; set; } = new(); + /// /// Invoked before a escapes. /// @@ -704,6 +709,12 @@ public class Player /// The instance. public static void OnRemovingHandcuffs(RemovingHandcuffsEventArgs ev) => RemovingHandcuffs.InvokeSafely(ev); + /// + /// Called after freeing a handcuffed . + /// + /// The instance. + public static void OnRemovedHandcuffs(RemovedHandcuffsEventArgs ev) => RemovedHandcuffs.InvokeSafely(ev); + /// /// Called before a escapes. /// 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/Handlers/Server.cs b/EXILED/Exiled.Events/Handlers/Server.cs index 1b3d0481a..75be4d81c 100644 --- a/EXILED/Exiled.Events/Handlers/Server.cs +++ b/EXILED/Exiled.Events/Handlers/Server.cs @@ -7,6 +7,9 @@ namespace Exiled.Events.Handlers { + using System.Collections.Generic; + + using Respawning; #pragma warning disable SA1623 // Property summary documentation should match accessors using Exiled.Events.EventArgs.Player; @@ -53,6 +56,11 @@ public static class Server /// public static Event RespawningTeam { get; set; } = new(); + /// + /// Invoked after team spawns. + /// + public static Event RespawnedTeam { get; set; } = new(); + /// /// Invoked before adding an unit name. /// @@ -142,6 +150,13 @@ public static class Server /// The instance. public static void OnRespawningTeam(RespawningTeamEventArgs ev) => RespawningTeam.InvokeSafely(ev); + /// + /// Called after team spawns. + /// + /// + /// + public static void OnRespawnedTeam(SpawnableTeamType teamType, List hubs) => RespawnedTeam.InvokeSafely(new RespawnedTeamEventArgs(teamType, hubs)); + /// /// Called before adding an unit name. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs b/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs index 3e49274a0..17394e444 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Item { using System.Collections.Generic; + using System.Linq; using System.Reflection.Emit; using API.Features; @@ -67,10 +68,8 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - Label continueLabel = generator.DefineLabel(); - LocalBuilder pickup = generator.DeclareLocal(typeof(ItemPickupBase)); - int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Dup); - - newInstructions.RemoveRange(index, 1); - - int offset = 1; - index = newInstructions.FindIndex(i => i.opcode == OpCodes.Dup) + offset; - newInstructions.InsertRange(index, new CodeInstruction[] - { - new(OpCodes.Dup), - new(OpCodes.Stloc_S, pickup.LocalIndex), - }); + Label continueLabel = generator.DefineLabel(); - offset = -2; - index = newInstructions.FindIndex(i => i.Calls(Method(typeof(NetworkServer), nameof(NetworkServer.Spawn), new[] { typeof(GameObject), typeof(NetworkConnection) }))) + offset; + int offset = -2; + int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(NetworkServer), nameof(NetworkServer.Spawn), new[] { typeof(GameObject), typeof(NetworkConnection) }))) + offset; newInstructions.InsertRange(index, new[] { - new CodeInstruction(OpCodes.Ldsfld, Field(typeof(Scp244Spawner), nameof(Scp244Spawner.CompatibleRooms))).MoveLabelsFrom(newInstructions[index]), + // save Pickup from the stack + new CodeInstruction(OpCodes.Stloc_S, pickup.LocalIndex).MoveLabelsFrom(newInstructions[index]), + + // Scp244Spawner.CompatibleRooms[num] + new(OpCodes.Ldsfld, Field(typeof(Scp244Spawner), nameof(Scp244Spawner.CompatibleRooms))), new(OpCodes.Ldloc_0), new(OpCodes.Callvirt, PropertyGetter(typeof(List), "Item")), - new(OpCodes.Ldloc_S, pickup.LocalIndex), - new(OpCodes.Call, Method(typeof(Pickup), nameof(Pickup.Get), new[] { typeof(ItemPickupBase) })), + // scp244DeployablePickup + new(OpCodes.Ldloc_2), + // Scp244SpawningEventArgs ev = new(Room, Scp244DeployablePickup) new(OpCodes.Newobj, GetDeclaredConstructors(typeof(Scp244SpawningEventArgs))[0]), new(OpCodes.Dup), new(OpCodes.Call, Method(typeof(Handlers.Map), nameof(Handlers.Map.OnScp244Spawning))), + // if (ev.IsAllowed) goto continueLabel; new(OpCodes.Call, PropertyGetter(typeof(Scp244SpawningEventArgs), nameof(Scp244SpawningEventArgs.IsAllowed))), new(OpCodes.Brtrue_S, continueLabel), - new(OpCodes.Ldloc_S, pickup.LocalIndex), + // scp244DeployablePickup.gameObject.Destroy() + // return; + new(OpCodes.Ldloc_2), new(OpCodes.Callvirt, PropertyGetter(typeof(ItemPickupBase), nameof(ItemPickupBase.gameObject))), new(OpCodes.Call, Method(typeof(NetworkServer), nameof(NetworkServer.Destroy))), new(OpCodes.Ret), - new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel), - new(OpCodes.Ldloc_S, pickup.LocalIndex), + // load pickup back into the stack + new CodeInstruction(OpCodes.Ldloc_S, pickup.LocalIndex).WithLabels(continueLabel), }); for (int z = 0; z < newInstructions.Count; z++) diff --git a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs index ff3cd5762..e65ced6fb 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs @@ -40,6 +40,7 @@ private static IEnumerable Transpiler(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 +123,7 @@ private static IEnumerable Transpiler(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 CodeInstruction(OpCodes.Stloc_S, fpcRole.LocalIndex).WithLabels(newInstructions[i].labels); + else if (opcode == OpCodes.Ldloc_0) + newInstructions[i] = new CodeInstruction(OpCodes.Ldloc_S, fpcRole.LocalIndex).WithLabels(newInstructions[i].labels); + 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/Player/FirearmRequestReceived.cs b/EXILED/Exiled.Events/Patches/Events/Player/FirearmRequestReceived.cs index 6ebd931f4..ff0dceace 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/FirearmRequestReceived.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/FirearmRequestReceived.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Player { using System.Collections.Generic; + using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -69,7 +70,7 @@ private static IEnumerable Transpiler(IEnumerable !x.IsGenericMethod && x.Name is nameof(API.Features.Items.Item.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemBase))), new(OpCodes.Isinst, typeof(Firearm)), new(OpCodes.Dup), new(OpCodes.Stloc_S, firearm.LocalIndex), 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 /// Patches . - /// Adds the and events. + /// Adds the , , and events. /// [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.Handcuffing))] [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.RemovingHandcuffs))] + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.RemovedHandcuffs))] [HarmonyPatch(typeof(DisarmingHandlers), nameof(DisarmingHandlers.ServerProcessDisarmMessage))] internal static class ProcessDisarmMessage { @@ -46,6 +48,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } + + /// + /// Patches . + /// Invokes and event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.RemovingHandcuffs))] + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.RemovedHandcuffs))] + [HarmonyPatch(typeof(DisarmedPlayers), nameof(DisarmedPlayers.ValidateEntry))] + internal static class Uncuff + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + Label returnLabel = generator.DefineLabel(); + + int offset = 2; + int index = newInstructions.FindLastIndex( + instruction => instruction.Calls(Method(typeof(ReferenceHub), nameof(ReferenceHub.TryGetHubNetID)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Invoking RemovingHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.CufferDied + new(OpCodes.Ldc_I4_2), + + // true + new(OpCodes.Ldc_I4_1), + + // RemovingHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.CufferDied, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingHandcuffsEventArgs))[0]), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // new(OpCodes.Dup), + + // Handlers.Player.OnRemovingHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovingHandcuffs))), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // if (!ev.IsAllowed) + // return true; + // new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingHandcuffsEventArgs), nameof(RemovingHandcuffsEventArgs.IsAllowed))), + // new(OpCodes.Brfalse_S, returnLabel), + + // Invoking RemovedHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.CufferDied + new(OpCodes.Ldc_I4_2), + + // RemovedHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.CufferDied) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovedHandcuffsEventArgs))[0]), + + // Handlers.Player.OnRemovedHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovedHandcuffs))), + }); + + offset = 5; + index = newInstructions.FindLastIndex( + instruction => instruction.Calls(PropertyGetter(typeof(PlayerRoles.PlayerRoleManager), nameof(PlayerRoles.PlayerRoleManager.CurrentRole)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Invoking RemovingHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.CufferDied + new(OpCodes.Ldc_I4_2), + + // true + new(OpCodes.Ldc_I4_1), + + // RemovingHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.CufferDied, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingHandcuffsEventArgs))[0]), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // new(OpCodes.Dup), + + // Handlers.Player.OnRemovingHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovingHandcuffs))), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // if (!ev.IsAllowed) + // return true; + // new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingHandcuffsEventArgs), nameof(RemovingHandcuffsEventArgs.IsAllowed))), + // new(OpCodes.Brfalse_S, returnLabel), + + // Invoking RemovedHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.CufferDied + new(OpCodes.Ldc_I4_2), + + // RemovedHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.CufferDied) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovedHandcuffsEventArgs))[0]), + + // Handlers.Player.OnRemovedHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovedHandcuffs))), + }); + + offset = 3; + index = newInstructions.FindLastIndex( + instruction => instruction.Calls(PropertyGetter(typeof(UnityEngine.Vector3), nameof(UnityEngine.Vector3.sqrMagnitude)))) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Invoking RemovingHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.OutOfRange + new(OpCodes.Ldc_I4_1), + + // true + new(OpCodes.Ldc_I4_1), + + // RemovingHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.OutOfRange, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingHandcuffsEventArgs))[0]), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // new(OpCodes.Dup), + + // Handlers.Player.OnRemovingHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovingHandcuffs))), + + // TODO: Uncomment this part in next major update to prevent breaking changes + // if (!ev.IsAllowed) + // return true; + // new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingHandcuffsEventArgs), nameof(RemovingHandcuffsEventArgs.IsAllowed))), + // new(OpCodes.Brfalse_S, returnLabel), + + // Invoking RemovedHandcuffs event + // Player.Get(Cuffer) + new CodeInstruction(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(Target) + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // UncuffReason.CufferDied + new(OpCodes.Ldc_I4_2), + + // RemovedHandcuffsEventArgs ev = new(Cuffer, Target, UncuffReason.OutOfRange) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovedHandcuffsEventArgs))[0]), + + // Handlers.Player.OnRemovedHandcuffs(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnRemovedHandcuffs))), + }); + + newInstructions[newInstructions.Count - 2].labels.Add(returnLabel); + + 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/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/Events/Scp049/FinishingRecall.cs b/EXILED/Exiled.Events/Patches/Events/Scp049/FinishingRecall.cs index d1458e0fd..2eab40acc 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp049/FinishingRecall.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp049/FinishingRecall.cs @@ -25,11 +25,8 @@ namespace Exiled.Events.Patches.Events.Scp049 /// /// Patches . /// Adds the event. - /// Fix bug than Overwatch can get force respawn by Scp049 - /// Bug reported to NW https://trello.com/c/V0uHP2eV/5745-overwatch-overwatch-can-get-respawned-by-scp-049. - /// The fix is directly inside the . /// - // [EventPatch(typeof(Handlers.Scp049), nameof(Handlers.Scp049.FinishingRecall))] + [EventPatch(typeof(Handlers.Scp049), nameof(Handlers.Scp049.FinishingRecall))] [HarmonyPatch(typeof(Scp049ResurrectAbility), nameof(Scp049ResurrectAbility.ServerComplete))] internal static class FinishingRecall { 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 Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(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 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(ReferenceHub), nameof(ReferenceHub.playerEffectsController)))) + serverEffectLocationStart; + newInstructions[enableEffect].WithLabels(enableEffectLabel); 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, enableEffectLabel), }); // 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 +109,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 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 diff --git a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs index bba54c92e..d672d1831 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 is byte and 4) + offset; Label ret = generator.DefineLabel(); 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/Fix106RegenerationWithScp244.cs b/EXILED/Exiled.Events/Patches/Fixes/Fix106RegenerationWithScp244.cs new file mode 100644 index 000000000..33c54d5ad --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/Fix106RegenerationWithScp244.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// 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 InventorySystem.Items.Usables.Scp244.Hypothermia; + using PlayerRoles; + using PlayerRoles.PlayableScps.Scp106; + + using static HarmonyLib.AccessTools; + + /// + /// Patches the delegate. + /// Fix than SCP-106 regenerates slower in SCP-244 even if they are in stalk. + /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/367). + /// + [HarmonyPatch(typeof(Hypothermia), nameof(Hypothermia.Update))] + internal class Fix106RegenerationWithScp244 + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder scp106Role = generator.DeclareLocal(typeof(Scp106Role)); + Label continueLabel = generator.DefineLabel(); + + int offset = 1; + int index = newInstructions.FindLastIndex(x => x.operand == (object)Method(typeof(SpawnProtected), nameof(SpawnProtected.CheckPlayer))) + offset; + + Label skip = (Label)newInstructions[index].operand; + + index += offset; + + newInstructions[index].labels.Add(continueLabel); + + newInstructions.InsertRange(index, new[] + { + // Scp106Role scp106Role = base.Hub.roleManager.CurrentRole as Scp106Role; + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(StatusEffectBase), nameof(StatusEffectBase.Hub))), + new CodeInstruction(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.roleManager))), + new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(PlayerRoleManager), nameof(PlayerRoleManager.CurrentRole))), + new CodeInstruction(OpCodes.Isinst, typeof(Scp106Role)), + new CodeInstruction(OpCodes.Stloc_S, scp106Role.LocalIndex), + + // if (scp106Role is null) goto continueLabel + new CodeInstruction(OpCodes.Ldloc_S, scp106Role.LocalIndex), + new CodeInstruction(OpCodes.Brfalse_S, continueLabel), + + // if (!scp106Role.IsSubmerged) goto skip + new CodeInstruction(OpCodes.Ldloc_S, scp106Role.LocalIndex), + new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.IsSubmerged))), + new CodeInstruction(OpCodes.Brtrue_S, skip), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Fixes/GrenadePropertiesFix.cs b/EXILED/Exiled.Events/Patches/Fixes/GrenadePropertiesFix.cs index 8c37c4f9a..e37fb8993 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/GrenadePropertiesFix.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/GrenadePropertiesFix.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Fixes { using System.Collections.Generic; + using System.Linq; using System.Reflection.Emit; using API.Features.Items; @@ -58,7 +59,7 @@ private static IEnumerable Transpiler(IEnumerable !x.IsGenericMethod && x.Name is nameof(Item.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemBase))), new(OpCodes.Isinst, typeof(Throwable)), new(OpCodes.Dup), new(OpCodes.Stloc_S, throwable.LocalIndex), diff --git a/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs b/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs new file mode 100644 index 000000000..680e40608 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Player +{ +#pragma warning disable IDE0060 + + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features; + using API.Features.Pools; + + using HarmonyLib; + using InventorySystem.Items.Jailbird; + using Mirror; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Bug reported to NW (https://trello.com/c/kyr3hV9B). + /// + [HarmonyPatch(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.Setup))] + internal static class Jailbird914CoarseFix + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = -1; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Blt_S) + offset; + + List [HarmonyPatch(typeof(FpcMotor), nameof(FpcMotor.UpdatePosition))] #pragma warning disable SA1402 // File may only contain a single type @@ -62,4 +64,4 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Fixes/WarheadConfigLockGateFix.cs b/EXILED/Exiled.Events/Patches/Fixes/WarheadConfigLockGateFix.cs new file mode 100644 index 000000000..2c65c58a8 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/WarheadConfigLockGateFix.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------- +// +// 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 Footprinting; + using HarmonyLib; + using Interactables.Interobjects.DoorUtils; + using InventorySystem; + using InventorySystem.Items.Firearms.Ammo; + using InventorySystem.Items.Pickups; + + using static HarmonyLib.AccessTools; + + /// + /// Patches delegate. + /// Fix than NW config "lock_gates_on_countdown" + /// reported https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/316. + /// + [HarmonyPatch(typeof(DoorEventOpenerExtension), nameof(DoorEventOpenerExtension.Trigger))] + internal class WarheadConfigLockGateFix + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + // replace Contains with StartWith + int index = newInstructions.FindIndex(x => x.operand == (object)Method(typeof(string), nameof(string.Contains), new[] { typeof(string) })); + newInstructions[index].operand = Method(typeof(string), nameof(string.StartsWith), new System.Type[] { typeof(string) }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Generic/DoorList.cs b/EXILED/Exiled.Events/Patches/Generic/DoorList.cs index 1b63ec329..4f1ce6e33 100644 --- a/EXILED/Exiled.Events/Patches/Generic/DoorList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/DoorList.cs @@ -50,7 +50,7 @@ private static void Postfix(DoorVariant __instance) foreach (DoorVariant subDoor in checkpoint.Base.SubDoors) { subDoor.RegisterRooms(); - BreakableDoor targetDoor = Door.Get(subDoor).Cast(); + BreakableDoor targetDoor = Door.Get(subDoor); targetDoor.ParentCheckpointDoor = checkpoint; checkpoint.SubDoorsValue.Add(targetDoor); 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); + } + } +} 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 diff --git a/EXILED/Exiled.Events/Patches/Generic/PickupControlPatch.cs b/EXILED/Exiled.Events/Patches/Generic/PickupControlPatch.cs index c76a8f247..fc319c474 100644 --- a/EXILED/Exiled.Events/Patches/Generic/PickupControlPatch.cs +++ b/EXILED/Exiled.Events/Patches/Generic/PickupControlPatch.cs @@ -9,6 +9,7 @@ namespace Exiled.Events.Patches.Generic { using System; using System.Collections.Generic; + using System.Linq; using System.Reflection.Emit; using API.Features.Pickups; @@ -48,11 +49,11 @@ private static IEnumerable Transpiler( { // pickup = Pickup.Get(pickupBase); new(OpCodes.Ldloc_0), - new(OpCodes.Call, Method(typeof(Pickup), nameof(Pickup.Get), new[] { typeof(ItemPickupBase) })), + new(OpCodes.Call, GetDeclaredMethods(typeof(Pickup)).First(x => !x.IsGenericMethod && x.Name is nameof(Pickup.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemPickupBase))), // Item.Get(itemBase); new(OpCodes.Ldarg_0), - new(OpCodes.Call, Method(typeof(Item), nameof(Item.Get), new[] { typeof(ItemBase) })), + new(OpCodes.Call, GetDeclaredMethods(typeof(Item)).First(x => !x.IsGenericMethod && x.Name is nameof(Item.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemBase))), // pickup.ReadItemInfo(item); new(OpCodes.Callvirt, Method(typeof(Pickup), nameof(Pickup.ReadItemInfo))), @@ -79,7 +80,7 @@ private static IEnumerable Transpiler(IEnumerable !x.IsGenericMethod && x.Name is nameof(Pickup.Get) && x.GetParameters().Length is 1 && x.GetParameters()[0].ParameterType == typeof(ItemPickupBase))), new(OpCodes.Pop), }); diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp049API/CallAbilityDuration.cs b/EXILED/Exiled.Events/Patches/Generic/Scp049API/CallAbilityDuration.cs deleted file mode 100644 index ddfb45adb..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp049API/CallAbilityDuration.cs +++ /dev/null @@ -1,63 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp079API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp049; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp049Role = PlayerRoles.PlayableScps.Scp049.Scp049Role; - using Scp049Role = API.Features.Roles.Scp049Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp049CallAbility), nameof(Scp049CallAbility.ServerProcessCmd))] - internal class CallAbilityDuration - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp049Role)); - - // replace "this.Duration.Trigger(20.0)" with "this.Duration.Trigger((Player.Get(base.Owner).Role as Scp049Role).CallAbilityDuration)" - int offset = -1; - int index = newInstructions.FindIndex(instruction => instruction.operand == (object)Method(typeof(AbilityCooldown), nameof(AbilityCooldown.Trigger))) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp049Role).CallAbilityDuration - new(OpCodes.Isinst, typeof(Scp049Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049Role), nameof(Scp049Role.CallAbilityDuration))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityBaseCooldown.cs b/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityBaseCooldown.cs deleted file mode 100644 index a70583b5f..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityBaseCooldown.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp079API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp049; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp049Role = PlayerRoles.PlayableScps.Scp049.Scp049Role; - using Scp049Role = API.Features.Roles.Scp049Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.ServerProcessKilledPlayer))] - internal class SenseAbilityBaseCooldown - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp049Role)); - - // replace "this.Duration.Trigger(40.0)" with "this.Duration.Trigger((Player.Get(base.Owner).Role as Scp049Role).SenseAbilityBaseCooldown)" - int offset = -1; - int index = newInstructions.FindIndex(instruction => instruction.operand == (object)Method(typeof(AbilityCooldown), nameof(AbilityCooldown.Trigger))) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp049Role).SenseAbilityBaseCooldown - new(OpCodes.Isinst, typeof(Scp049Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049Role), nameof(Scp049Role.SenseAbilityBaseCooldown))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityReducedCooldown.cs b/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityReducedCooldown.cs deleted file mode 100644 index 88c8333a1..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp049API/SenseAbilityReducedCooldown.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp079API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp049; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp049Role = PlayerRoles.PlayableScps.Scp049.Scp049Role; - using Scp049Role = API.Features.Roles.Scp049Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.ServerLoseTarget))] - internal class SenseAbilityReducedCooldown - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp049Role)); - - // replace "this.Duration.Trigger(40.0)" with "this.Duration.Trigger((Player.Get(base.Owner).Role as Scp049Role).SenseAbilityReducedCooldown)" - int offset = -1; - int index = newInstructions.FindIndex(instruction => instruction.operand == (object)Method(typeof(AbilityCooldown), nameof(AbilityCooldown.Trigger))) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp049Role).SenseAbilityReducedCooldown - new(OpCodes.Isinst, typeof(Scp049Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049Role), nameof(Scp049Role.SenseAbilityReducedCooldown))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/CooldownReductionReward.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/CooldownReductionReward.cs deleted file mode 100644 index 4f509aad2..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/CooldownReductionReward.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp106Attack), nameof(Scp106Attack.ReduceSinkholeCooldown))] - internal class CooldownReductionReward - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "base.ScpRole.Sinkhole.Cooldown.NextUse -= 5.0; - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // replace "base.ScpRole.Sinkhole.Cooldown.NextUse -= scp106Role.CooldownReductionReward; - int offset = 0; - int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).CooldownReductionReward - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.CooldownReductionReward))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/CustomAttack.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/CustomAttack.cs deleted file mode 100644 index f6469919a..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/CustomAttack.cs +++ /dev/null @@ -1,111 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the , , property. - /// - // [HarmonyPatch(typeof(Scp106Attack), nameof(Scp106Attack.ServerShoot))] - internal class CustomAttack - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "base.Vigor.VigorAmount += 0.3f;" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // "base.Vigor.VigorAmount += scp106Role.VigorCaptureReward;" - int offset = 0; - int index = newInstructions.FindIndex(instruction => instruction.operand == (object)Scp106Attack.VigorCaptureReward) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).AttackDamage - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.VigorCaptureReward))), - }); - - // replace "base.Vigor.VigorAmount += 0.3f;" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // "base.Vigor.VigorAmount += scp106Role.VigorCaptureReward;" - offset = 0; - index = newInstructions.FindIndex(instruction => instruction.operand == (object)Scp106Attack.VigorCaptureReward) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).AttackDamage - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.VigorCaptureReward))), - }); - - // replace "playerEffectsController.EnableEffect(20f, false);" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // "playerEffectsController.EnableEffect(scp106Role.CorrodingTime, false);" - offset = 0; - index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).CorrodingTime - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.CorrodingTime))), - }); - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/HunterAtlastCostPerMetter.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/HunterAtlastCostPerMetter.cs deleted file mode 100644 index d85ac0151..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/HunterAtlastCostPerMetter.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp106HuntersAtlasAbility), nameof(Scp106HuntersAtlasAbility.ServerProcessCmd))] - internal class HunterAtlastCostPerMetter - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "num = (position2 - this._syncPos).MagnitudeIgnoreY() * 0.019f;" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // replace "num = (position2 - this._syncPos).MagnitudeIgnoreY() * scp106Role.HuntersAtlasCostPerMeter;" - int offset = 0; - int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).HuntersAtlasCostPerMeter - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.HuntersAtlasCostPerMeter))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/SinkholeAbilityCooldown.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/SinkholeAbilityCooldown.cs deleted file mode 100644 index 519fef858..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/SinkholeAbilityCooldown.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp106Attack), nameof(Scp106Attack.ReduceSinkholeCooldown))] - internal class SinkholeAbilityCooldown - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "this.Cooldown.Trigger(20.0);" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // replace "this.Cooldown.Trigger(scp106Role.SinkholeCooldownDuration);" - int offset = 0; - int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).SinkholeCooldownDuration - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.SinkholeCooldownDuration))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/StalkVigorUse.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/StalkVigorUse.cs deleted file mode 100644 index 38b7a9e53..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/StalkVigorUse.cs +++ /dev/null @@ -1,84 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the and property. - /// - // [HarmonyPatch(typeof(Scp106StalkAbility), nameof(Scp106StalkAbility.UpdateServerside))] - internal class StalkVigorUse - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "float num = base.ScpRole.FpcModule.Motor.MovementDetected ? 0.09f : 0.06f;" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // "float num = base.ScpRole.FpcModule.Motor.MovementDetected ? scp106Role.VigorStalkCostMoving : scp106Role.VigorStalkCostStationary;" - int offset = 0; - int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).VigorStalkCostMoving - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.VigorStalkCostMoving))), - }); - - offset = 0; - index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ldc_R4) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).VigorStalkCostStationary - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.VigorStalkCostStationary))), - }); - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp106API/VigorRegeneration.cs b/EXILED/Exiled.Events/Patches/Generic/Scp106API/VigorRegeneration.cs deleted file mode 100644 index f14fd51d1..000000000 --- a/EXILED/Exiled.Events/Patches/Generic/Scp106API/VigorRegeneration.cs +++ /dev/null @@ -1,66 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Generic.Scp106API -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Exiled.API.Features; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp106; - using PlayerRoles.Subroutines; - - using static HarmonyLib.AccessTools; - - using BaseScp106Role = PlayerRoles.PlayableScps.Scp106.Scp106Role; - using Scp106Role = API.Features.Roles.Scp106Role; - - /// - /// Patches . - /// Adds the property. - /// - // [HarmonyPatch(typeof(Scp106StalkAbility), nameof(Scp106StalkAbility.UpdateMovementState))] - internal class VigorRegeneration - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - LocalBuilder scp049Role = generator.DeclareLocal(typeof(Scp106Role)); - - // replace "base.Vigor.VigorAmount += 0.03f * Time.deltaTime;" - // with - // Scp106Role scp106Role = Player.Get(this.Owner).Role.As() - // "base.Vigor.VigorAmount += scp106Role.VigorRegeneration * Time.deltaTime;" - int offset = -4; - int index = newInstructions.FindIndex(instruction => instruction.operand == (object)PropertySetter(typeof(Scp106VigorAbilityBase), nameof(Scp106VigorAbilityBase.VigorAmount))) + offset; - newInstructions.RemoveAt(index); - - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // Player.Get(base.Owner).Role - new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(Player), nameof(Player.Role))), - - // (Player.Get(base.Owner).Role as Scp106Role).VigorRegeneration - new(OpCodes.Isinst, typeof(Scp106Role)), - new(OpCodes.Callvirt, PropertyGetter(typeof(Scp106Role), nameof(Scp106Role.VigorRegeneration))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Installer/Program.cs b/EXILED/Exiled.Installer/Program.cs index 34912cc51..559c17c82 100644 --- a/EXILED/Exiled.Installer/Program.cs +++ b/EXILED/Exiled.Installer/Program.cs @@ -42,7 +42,7 @@ internal enum PathResolution internal static class Program { - private const long RepoID = 828620622; + private const long RepoID = 833723500; private const string ExiledAssetName = "exiled.tar.gz"; // This is the lowest version the installer will check to install @@ -303,4 +303,4 @@ private static Release FindRelease(CommandSettings args, IEnumerable re return enumerable.First(); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 6990c1812..fb0636570 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -31,7 +31,7 @@ namespace Exiled.Loader /// internal sealed class Updater { - private const long REPOID = 828620622; + private const long REPOID = 833723500; private const string INSTALLER_ASSET_NAME_LINUX = "Exiled.Installer-Linux"; private const string INSTALLER_ASSET_NAME_WIN = "Exiled.Installer-Win.exe"; @@ -332,4 +332,4 @@ private bool FindAsset(string assetName, Release release, out ReleaseAsset asset return false; } } -} \ No newline at end of file +}