diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index ab7b2d0556..7ee77d48a0 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -16,9 +16,9 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exslmod-team.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/ExSLMod-Team/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: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 728da76f7f..d23c64887c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ permissions: id-token: write env: - EXILED_REFERENCES_URL: https://exslmod-team.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 # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. diff --git a/.github/workflows/labapi.yml b/.github/workflows/labapi.yml deleted file mode 100644 index 69cb71f0aa..0000000000 --- a/.github/workflows/labapi.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Exiled Dev CI - -on: - push: - branches: - - LabAPI - pull_request: - branches: - - LabAPI - workflow_dispatch: - -defaults: - run: - working-directory: ./EXILED - -env: - EXILED_REFERENCES_URL: https://exslmod-team.github.io/SL-References/LabAPI.zip - EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References - EXILED_DLL_ARCHIVER_URL: https://github.com/ExSLMod-Team/EXILED-DLL-Archiver/releases/download/v1.8.2/EXILED-DLL-Archiver.exe - -jobs: - - build: - - runs-on: windows-latest - # Prevent double running for push & pull_request events from the main repo - if: github.event_name != 'push' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - - steps: - - - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v4.0.1 - - - name: Setup Nuget - uses: nuget/setup-nuget@v2 - - - uses: actions/checkout@v4.1.7 - - - name: Get references - shell: pwsh - run: | - Invoke-WebRequest -Uri ${{ env.EXILED_REFERENCES_URL }} -OutFile ${{ github.workspace }}/EXILED/References.zip - Expand-Archive -Path References.zip -DestinationPath ${{ env.EXILED_REFERENCES_PATH }} - - - name: Build - env: - EXILED_REFERENCES: ${{ env.EXILED_REFERENCES_PATH }} - shell: pwsh - run: | - ./build.ps1 -BuildNuGet - $File = (Get-ChildItem -Path . -Include 'ExMod.Exiled.*.nupkg' -Recurse).Name - Out-File -FilePath ${{ github.env }} -InputObject "PackageFile=$File" -Encoding utf-8 -Append - - - name: Upload nuget package - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PackageFile }} - path: EXILED/${{ env.PackageFile }} - - - name: Get references - shell: pwsh - run: | - Invoke-WebRequest -Uri ${{ env.EXILED_DLL_ARCHIVER_URL }} -OutFile ${{ github.workspace }}/EXILED/EXILED-DLL-Archiver.exe - - - name: Packaging results as tar.gz - shell: pwsh - run: ./packaging.ps1 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: Build Result - path: EXILED/bin/Release/Exiled.tar.gz - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 576a58f46b..0789a2649d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exslmod-team.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/push_nuget.yml b/.github/workflows/push_nuget.yml index 35bf02b145..ea82f96926 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://exslmod-team.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 jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df466d0209..c4edbf2994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,9 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exslmod-team.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/ExSLMod-Team/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/.gitignore b/.gitignore index 958e3428c7..722b747e7c 100644 --- a/.gitignore +++ b/.gitignore @@ -376,4 +376,7 @@ _site/ JSON/ # Mac DS_Store -.DS_Store \ No newline at end of file +.DS_Store + +# Code Rush +/EXILED/.cr/* diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index eefc8a1140..02d86cf5cc 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.0 + 9.6.3 false @@ -25,8 +25,8 @@ Copyright © $(Authors) 2020 - $([System.DateTime]::Now.ToString("yyyy")) Git - https://github.com/ExSLMod-Team/EXILED - https://github.com/ExSLMod-Team/EXILED + https://github.com/Exmod-Team/EXILED + https://github.com/Exmod-Team/EXILED CC-BY-SA-3.0 $(DefineConstants);PUBLIC_BETA diff --git a/EXILED/Exiled.API/Enums/AspectRatioType.cs b/EXILED/Exiled.API/Enums/AspectRatioType.cs new file mode 100644 index 0000000000..79e4f4c96e --- /dev/null +++ b/EXILED/Exiled.API/Enums/AspectRatioType.cs @@ -0,0 +1,60 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// All available screen aspect ratio types. + /// + public enum AspectRatioType : byte + { + /// + /// Unknown aspect ratio. + /// + Unknown, + + /// + /// 1:1 aspect ratio (square screen). + /// + Ratio1_1, + + /// + /// 3:2 aspect ratio. + /// + Ratio3_2, + + /// + /// 4:3 aspect ratio (standard definition TVs, older monitors). + /// + Ratio4_3, + + /// + /// 5:4 aspect ratio (some older computer monitors). + /// + Ratio5_4, + + /// + /// 16:9 aspect ratio (modern widescreen displays, HDTV). + /// + Ratio16_9, + + /// + /// 16:10 aspect ratio (common in productivity monitors and laptops). + /// + Ratio16_10, + + /// + /// 21:9 aspect ratio (ultrawide displays). + /// + Ratio21_9, + + /// + /// 32:9 aspect ratio (super ultrawide displays). + /// + Ratio32_9, + } +} diff --git a/EXILED/Exiled.API/Enums/DamageType.cs b/EXILED/Exiled.API/Enums/DamageType.cs index ae93c5c020..e1054e1f33 100644 --- a/EXILED/Exiled.API/Enums/DamageType.cs +++ b/EXILED/Exiled.API/Enums/DamageType.cs @@ -15,8 +15,7 @@ namespace Exiled.API.Enums /// Identifiers for types of damage. /// /// - /// - /// + /// /// public enum DamageType { @@ -275,4 +274,4 @@ public enum DamageType /// Scp127, } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.API/Enums/SpawnReason.cs b/EXILED/Exiled.API/Enums/SpawnReason.cs index 8274b99336..3bde3457df 100644 --- a/EXILED/Exiled.API/Enums/SpawnReason.cs +++ b/EXILED/Exiled.API/Enums/SpawnReason.cs @@ -57,9 +57,19 @@ public enum SpawnReason : byte // TOTO: Remove this file and use Basegame /// Destroyed, + /// + /// The player was dead and is respawning in a mini-wave. + /// + RespawnMiniwave, + /// /// The user has been spawn by the usage of an Item. /// ItemUsage, + + /// + /// The user has been spawn by Exiled CustomRole. + /// + CustomRole, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 6abeea8a2f..eaba7fe2bd 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -124,12 +124,12 @@ public static class DamageTypeExtensions /// Check if a damage type is caused by a weapon. /// /// The damage type to be checked. - /// Indicates whether the MicroHid damage type should be taken into account. + /// Indicates whether the MicroHid and Jailbird damage type should be taken into account. /// Returns whether the is caused by weapon. - public static bool IsWeapon(this DamageType type, bool checkMicro = true) => type switch + public static bool IsWeapon(this DamageType type, bool checkNonFirearm = true) => type switch { DamageType.Crossvec or DamageType.Logicer or DamageType.Revolver or DamageType.Shotgun or DamageType.AK or DamageType.Com15 or DamageType.Com18 or DamageType.E11Sr or DamageType.Fsp9 or DamageType.ParticleDisruptor or DamageType.Com45 or DamageType.Frmg0 or DamageType.A7 => true, - DamageType.MicroHid when checkMicro => true, + DamageType.MicroHid or DamageType.Jailbird when checkNonFirearm => true, _ => false, }; diff --git a/EXILED/Exiled.API/Extensions/FloatExtensions.cs b/EXILED/Exiled.API/Extensions/FloatExtensions.cs new file mode 100644 index 0000000000..b7a32af87f --- /dev/null +++ b/EXILED/Exiled.API/Extensions/FloatExtensions.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + using System.Collections.Generic; + + using Exiled.API.Enums; + + /// + /// A set of extensions for . + /// + public static class FloatExtensions + { + private static readonly Dictionary AspectRatioReferences = new() + { + { AspectRatioType.Unknown, 0f }, + { AspectRatioType.Ratio1_1, 1f }, + { AspectRatioType.Ratio3_2, 3f / 2f }, + { AspectRatioType.Ratio4_3, 4f / 3f }, + { AspectRatioType.Ratio5_4, 5f / 4f }, + { AspectRatioType.Ratio16_9, 16f / 9f }, + { AspectRatioType.Ratio16_10, 16f / 10f }, + { AspectRatioType.Ratio21_9, 21f / 9f }, + { AspectRatioType.Ratio32_9, 32f / 9f }, + }; + + /// + /// Gets the closest for a given aspect ratio value. + /// + /// The aspect ratio value to compare. + /// The closest matching . + public static AspectRatioType GetAspectRatioLabel(this float ratio) + { + float closestDiff = float.MaxValue; + AspectRatioType closestRatio = AspectRatioType.Unknown; + + foreach (KeyValuePair kvp in AspectRatioReferences) + { + float diff = Math.Abs(ratio - kvp.Value); + if (diff < closestDiff) + { + closestDiff = diff; + closestRatio = kvp.Key; + } + } + + return closestRatio; + } + } +} diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs index 458fd10bc4..1c03c97d9c 100644 --- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs +++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs @@ -38,9 +38,9 @@ public static class ItemExtensions /// Check if an item is a weapon. /// /// The item to be checked. - /// Indicates whether the MicroHID item should be taken into account. + /// Indicates whether the MicroHID and Jailbird item should be taken into account. /// Returns whether the is a weapon. - public static bool IsWeapon(this ItemType type, bool checkMicro = true) => type.GetFirearmType() is not FirearmType.None || (checkMicro && type is ItemType.MicroHID); + public static bool IsWeapon(this ItemType type, bool checkNonFirearm = true) => type.GetFirearmType() is not FirearmType.None || (checkNonFirearm && type is ItemType.MicroHID or ItemType.Jailbird); /// /// Check if an item is an SCP. diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 3244663a3e..1ab332af2a 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -367,10 +367,11 @@ public static void PlayCassieAnnouncement(this Player player, string words, bool /// Target to send. /// The message to be reproduced. /// The translation should be show in the subtitles. + /// The custom subtitles to show. /// Same on 's isHeld. /// Same on 's isNoisy. /// Same on 's isSubtitles. - public static void MessageTranslated(this Player player, string words, string translation, bool makeHold = false, bool makeNoise = true, bool isSubtitles = true) + public static void MessageTranslated(this Player player, string words, string translation, string customSubtitles, bool makeHold = false, bool makeNoise = true, bool isSubtitles = true) { StringBuilder announcement = StringBuilderPool.Pool.Get(); @@ -386,7 +387,7 @@ public static void MessageTranslated(this Player player, string words, string tr { if (controller != null) { - SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), message, makeHold, makeNoise, isSubtitles); + SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), message, makeHold, makeNoise, isSubtitles, customSubtitles); } } } diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index cfb6b3bbca..9fc693b7ee 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -28,8 +28,26 @@ public class KeybindSetting : SettingBase, IWrapper /// /// /// - public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) - : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, hintDescription), header, onChanged) + [Obsolete("This method will be removed next major version because of a new feature. Use the constructor with \"allowSpectator\" instead.")] + public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI, string hintDescription, HeaderSetting header, Action onChanged) + : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, false, hintDescription), header, onChanged) + { + Base = (SSKeybindSetting)base.Base; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, bool allowSpectatorTrigger = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) + : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, allowSpectatorTrigger, hintDescription), header, onChanged) { Base = (SSKeybindSetting)base.Base; } @@ -61,6 +79,15 @@ public bool PreventInteractionOnGUI set => Base.PreventInteractionOnGUI = value; } + /// + /// Gets or sets a value indicating whether the interaction is prevented in spectator roles. + /// + public bool AllowSpectatorTrigger + { + get => Base.AllowSpectatorTrigger; + set => Base.AllowSpectatorTrigger = value; + } + /// /// Gets or sets the assigned key. /// diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index bd858b3447..4d74256cca 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -12,6 +12,7 @@ namespace Exiled.API.Features.DamageHandlers using Footprinting; using Items; + using PlayerRoles; using PlayerRoles.PlayableScps.Scp096; using PlayerRoles.PlayableScps.Scp939; @@ -29,6 +30,7 @@ public class GenericDamageHandler : CustomReasonDamageHandler private Player player; private DamageType damageType; private DamageHandlerBase.CassieAnnouncement customCassieAnnouncement; + private bool overrideCassieForAllRole; /// /// Initializes a new instance of the class. @@ -40,11 +42,13 @@ public class GenericDamageHandler : CustomReasonDamageHandler /// Damage type. /// Custom cassie announcment. /// Text to provide to player death screen. - public GenericDamageHandler(Player player, Player attacker, float damage, DamageType damageType, DamageHandlerBase.CassieAnnouncement cassieAnnouncement, string damageText = null) + /// Whether to play Cassie for non-SCPs as well. + public GenericDamageHandler(Player player, Player attacker, float damage, DamageType damageType, DamageHandlerBase.CassieAnnouncement cassieAnnouncement, string damageText = null, bool overrideCassieForAllRole = false) : base(DamageTextDefault) { this.player = player; this.damageType = damageType; + this.overrideCassieForAllRole = overrideCassieForAllRole; cassieAnnouncement ??= DamageHandlerBase.CassieAnnouncement.Default; customCassieAnnouncement = cassieAnnouncement; @@ -237,7 +241,7 @@ public override HandlerOutput ApplyDamage(ReferenceHub ply) HandlerOutput output = base.ApplyDamage(ply); if (output is HandlerOutput.Death) { - if (customCassieAnnouncement?.Announcement != null) + if (customCassieAnnouncement?.Announcement != null && (overrideCassieForAllRole || ply.IsSCP())) { Cassie.Message(customCassieAnnouncement.Announcement); } diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index c2cea9a761..7897d2dd94 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -245,6 +245,24 @@ public DoorPermissionFlags RequiredPermissions set => Base.RequiredPermissions.RequiredPermissions = value; } + /// + /// Gets or sets a value indicating whether all required permissions must be present to open the door. + /// + public bool RequireAllPermissions + { + get => Base.RequiredPermissions.RequireAll; + set => Base.RequiredPermissions.RequireAll = value; + } + + /// + /// Gets or sets the permissions policy used to determine access to the door. + /// + public DoorPermissionsPolicy PermissionsPolicy + { + get => Base.RequiredPermissions; + set => Base.RequiredPermissions = value; + } + /// /// Gets or sets the door's rotation. /// @@ -600,7 +618,7 @@ private DoorType GetDoorType() { RoomType.EzCheckpointHallwayA => DoorType.CheckpointGateA, RoomType.EzCheckpointHallwayB => DoorType.CheckpointGateB, - RoomType.Hcz049 => Position.y < -805 ? DoorType.Scp049Gate : DoorType.Scp173NewGate, + RoomType.Hcz049 => Position.y < -10 ? DoorType.Scp049Gate : DoorType.Scp173NewGate, _ => DoorType.UnknownGate, }, "Elevator Door" or "Nuke Elevator Door" or "Elevator Door 02" => (Base as Interactables.Interobjects.ElevatorDoor)?.Group switch diff --git a/EXILED/Exiled.API/Features/Items/Throwable.cs b/EXILED/Exiled.API/Features/Items/Throwable.cs index 548b1037e7..721167a3b5 100644 --- a/EXILED/Exiled.API/Features/Items/Throwable.cs +++ b/EXILED/Exiled.API/Features/Items/Throwable.cs @@ -92,6 +92,11 @@ public void Throw(bool fullForce = true) Base.ServerThrow(settings.StartVelocity, settings.UpwardsFactor, settings.StartTorque, ThrowableNetworkHandler.GetLimitedVelocity(Owner?.Velocity ?? Vector3.one)); } + /// + /// Cancel the the throws of the item. + /// + public void CancelThrow() => Base.ServerProcessCancellation(); + /// /// Clones current object. /// diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index 43d83f09ad..01f0219a97 100644 --- a/EXILED/Exiled.API/Features/Lift.cs +++ b/EXILED/Exiled.API/Features/Lift.cs @@ -137,6 +137,7 @@ public ElevatorSequence Status ElevatorGroup.Scp049 => ElevatorType.Scp049, ElevatorGroup.GateA => ElevatorType.GateA, ElevatorGroup.GateB => ElevatorType.GateB, + ElevatorGroup.ServerRoom => ElevatorType.ServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => ElevatorType.LczA, ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => ElevatorType.LczB, ElevatorGroup.Nuke01 or ElevatorGroup.Nuke02 => ElevatorType.Nuke, diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index add2a5123b..7bd9a19193 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -67,7 +67,7 @@ DecontaminationController.Singleton.NetworkDecontaminationOverride is Decontamin /// /// The remaining time in seconds for the decontamination process. /// - public static float RemainingDecontaminationTime => Mathf.Min(0, (float)(DecontaminationController.Singleton.DecontaminationPhases[DecontaminationController.Singleton.DecontaminationPhases.Length - 1].TimeTrigger - DecontaminationController.GetServerTime)); + public static float RemainingDecontaminationTime => Mathf.Max(0, (float)(DecontaminationController.Singleton.DecontaminationPhases[DecontaminationController.Singleton.DecontaminationPhases.Length - 1].TimeTrigger - DecontaminationController.GetServerTime)); /// /// Gets all objects. @@ -117,13 +117,12 @@ public static bool IsDecontaminationEnabled public static void StaffMessage(string message, Player player = null) { player ??= Server.Host; - foreach (Player target in Player.List) { if (!CommandProcessor.CheckPermissions(target.Sender, PlayerPermissions.AdminChat)) continue; - player.ReferenceHub.encryptedChannelManager.TrySendMessageToClient(player.NetId + "!" + message, EncryptedChannelManager.EncryptedChannel.AdminChat); + target.ReferenceHub.encryptedChannelManager.TrySendMessageToClient(player.NetId + "!" + message, EncryptedChannelManager.EncryptedChannel.AdminChat); } } diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 4dfcf09409..1689744773 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -343,6 +343,11 @@ public PlayerInfoArea InfoArea set => ReferenceHub.nicknameSync.Network_playerInfoToShow = value; } + /// + /// Gets the player's current aspect ratio type. + /// + public AspectRatioType AspectRatio => ReferenceHub.aspectRatioSync.AspectRatio.GetAspectRatioLabel(); + /// /// Gets or sets the player's custom player info string. This string is displayed along with the player's . /// @@ -2144,9 +2149,21 @@ public void Hurt(Player attacker, float amount, DamageType damageType = DamageTy /// The of the damage dealt. /// The cassie announcement to make if the damage kills the player. /// The death text to appear on screen. - public void Hurt(Player attacker, float amount, DamageType damageType = DamageType.Unknown, CassieAnnouncement cassieAnnouncement = null, string deathText = null) => + public void Hurt(Player attacker, float amount, DamageType damageType, CassieAnnouncement cassieAnnouncement, string deathText) => Hurt(new GenericDamageHandler(this, attacker, amount, damageType, cassieAnnouncement, deathText)); + /// + /// Hurts the player. + /// + /// The attacking player. + /// The amount of damage to deal. + /// The of the damage dealt. + /// The cassie announcement to make if the damage kills the player. + /// The death text to appear on screen. + /// Whether to play Cassie for non-SCPs as well. + public void Hurt(Player attacker, float amount, DamageType damageType, CassieAnnouncement cassieAnnouncement, string deathText, bool overrideCassieForAllRole) => + Hurt(new GenericDamageHandler(this, attacker, amount, damageType, cassieAnnouncement, deathText, overrideCassieForAllRole)); + /// /// Hurts the player. /// @@ -3020,9 +3037,36 @@ public void ThrowItem(Throwable throwable, bool fullForce = true) /// The message to be shown. /// The duration the text will be on screen. public void ShowHint(string message, float duration = 3f) + { + ShowHint(message, new HintParameter[] { new StringHintParameter(message) }, null, duration); + } + + /// + /// Shows a hint to the player with the specified message, hint effects, and duration. + /// + /// The message to be shown as a hint. + /// The array of hint effects to apply. + /// The duration the hint will be displayed, in seconds. + public void ShowHint(string message, HintEffect[] hintEffects, float duration = 3f) + { + ShowHint(message, new HintParameter[] { new StringHintParameter(message) }, hintEffects, duration); + } + + /// + /// Shows a hint to the player with the specified message, hint parameters, hint effects, and duration. + /// + /// The message to be shown as a hint. + /// The array of hint parameters to use. + /// The array of hint effects to apply. + /// The duration the hint will be displayed, in seconds. + public void ShowHint(string message, HintParameter[] hintParameters, HintEffect[] hintEffects, float duration = 3f) { message ??= string.Empty; - HintDisplay.Show(new TextHint(message, new HintParameter[] { new StringHintParameter(message) }, null, duration)); + HintDisplay.Show(new TextHint( + message, + (!hintParameters.IsEmpty()) ? hintParameters : new HintParameter[] { new StringHintParameter(message) }, + hintEffects, + duration)); } /// diff --git a/EXILED/Exiled.API/Features/PrefabHelper.cs b/EXILED/Exiled.API/Features/PrefabHelper.cs index 82180ac9a6..cb17160b50 100644 --- a/EXILED/Exiled.API/Features/PrefabHelper.cs +++ b/EXILED/Exiled.API/Features/PrefabHelper.cs @@ -9,6 +9,7 @@ namespace Exiled.API.Features { using System; using System.Collections.Generic; + using System.Linq; using System.Reflection; using Exiled.API.Enums; @@ -24,12 +25,17 @@ public static class PrefabHelper /// /// A containing all and their corresponding . /// - internal static readonly Dictionary Prefabs = new(Enum.GetValues(typeof(PrefabType)).Length); + internal static readonly Dictionary Prefabs = new(Enum.GetValues(typeof(PrefabType)).Length); /// /// Gets a of and their corresponding . /// - public static IReadOnlyDictionary PrefabToGameObject => Prefabs; + public static IReadOnlyDictionary PrefabToGameObjectAndComponent => Prefabs; + + /// + /// Gets a of and their corresponding . + /// + public static IReadOnlyDictionary PrefabToGameObject => Prefabs.ToDictionary(x => x.Key, x => x.Value.Item1); /// /// Gets the from a . @@ -49,8 +55,8 @@ public static PrefabAttribute GetPrefabAttribute(this PrefabType prefabType) /// Returns the . public static GameObject GetPrefab(PrefabType prefabType) { - if (Prefabs.TryGetValue(prefabType, out GameObject prefab)) - return prefab; + if (Prefabs.TryGetValue(prefabType, out (GameObject, Component) prefab)) + return prefab.Item1; return null; } @@ -76,8 +82,8 @@ public static bool TryGetPrefab(PrefabType prefabType, out GameObject gameObject public static T GetPrefab(PrefabType prefabType) where T : Component { - if (Prefabs.TryGetValue(prefabType, out GameObject prefab) && prefab.TryGetComponent(out T component)) - return component; + if (Prefabs.TryGetValue(prefabType, out (GameObject, Component) prefab)) + return (T)prefab.Item2; return null; } diff --git a/EXILED/Exiled.API/Features/Ragdoll.cs b/EXILED/Exiled.API/Features/Ragdoll.cs index 011c092248..51878021cf 100644 --- a/EXILED/Exiled.API/Features/Ragdoll.cs +++ b/EXILED/Exiled.API/Features/Ragdoll.cs @@ -214,6 +214,11 @@ public bool IsConsumed } } + /// + /// Gets a value indicating whether this ragdoll is spawned. + /// + public bool IsSpawned => NetworkServer.spawned.ContainsValue(Base.netIdentity); + /// /// Gets the the ragdoll is located in. /// @@ -232,11 +237,17 @@ public Vector3 Position get => Base.transform.position; set { - NetworkServer.UnSpawn(GameObject); + if (!IsSpawned) + { + Base.transform.position = value; + return; + } + + UnSpawn(); Base.transform.position = value; - NetworkServer.Spawn(GameObject); + Spawn(); } } @@ -248,11 +259,17 @@ public Quaternion Rotation get => Base.transform.rotation; set { - NetworkServer.UnSpawn(GameObject); + if (!IsSpawned) + { + Base.transform.rotation = value; + return; + } + + UnSpawn(); Base.transform.rotation = value; - NetworkServer.Spawn(GameObject); + Spawn(); } } @@ -264,11 +281,17 @@ public Vector3 RagdollScale get => Base.transform.localScale; set { - NetworkServer.UnSpawn(GameObject); + if (!IsSpawned) + { + Base.transform.localScale = value; + return; + } + + UnSpawn(); Base.transform.localScale = value; - NetworkServer.Spawn(GameObject); + Spawn(); } } @@ -406,15 +429,40 @@ public static Ragdoll Get(BasicRagdoll ragdoll) => ragdoll == null ? null : public static IEnumerable Get(IEnumerable players) => players.SelectMany(pl => Ragdoll.List.Where(rd => rd.Owner == pl)); /// - /// Destroys the ragdoll. + /// Destroys the ragdoll immediately. /// public void Destroy() => Object.Destroy(GameObject); /// - /// Spawns the ragdoll. + /// Destroys the ragdoll after a specified delay. + /// + /// The delay in seconds before the ragdoll is destroyed. + public void Destroy(float delay) => Object.Destroy(GameObject, delay); + + /// + /// Spawns the ragdoll on the network. /// public void Spawn() => NetworkServer.Spawn(GameObject); + /// + /// Spawns the ragdoll on the network with a specified owner. + /// + /// The owner of the ragdoll. + public void Spawn(GameObject ownerPlayer) => NetworkServer.Spawn(GameObject, ownerPlayer); + + /// + /// Spawns the ragdoll on the network with a specified network connection or asset ID. + /// + /// The network connection of the owner. + /// The optional asset ID of the ragdoll. + public void Spawn(NetworkConnection ownerConnection, uint? assetId = null) + { + if (assetId.HasValue) + NetworkServer.Spawn(GameObject, assetId.Value, ownerConnection); + else + NetworkServer.Spawn(GameObject, ownerConnection); + } + /// /// Un-spawns the ragdoll. /// diff --git a/EXILED/Exiled.API/Features/Respawn.cs b/EXILED/Exiled.API/Features/Respawn.cs index 2430d5bd9b..0f34495520 100644 --- a/EXILED/Exiled.API/Features/Respawn.cs +++ b/EXILED/Exiled.API/Features/Respawn.cs @@ -388,6 +388,26 @@ public static void ForceWave(SpawnableWaveBase spawnableWaveBase) WaveManager.Spawn(spawnableWaveBase); } + /// + /// Pauses a specific respawn wave by removing it from the active wave list and adding it to the paused wave list. + /// + /// The representing the wave to pause. + public static void PauseWave(SpawnableFaction spawnableFaction) + { + if (TryGetWaveBase(spawnableFaction, out SpawnableWaveBase spawnableWaveBase)) + { + if (!PausedWaves.Contains(spawnableWaveBase)) + { + PausedWaves.Add(spawnableWaveBase); + } + + if (WaveManager.Waves.Contains(spawnableWaveBase)) + { + WaveManager.Waves.Remove(spawnableWaveBase); + } + } + } + /// /// Pauses respawn waves by removing them from WaveManager.Waves and storing them in . /// @@ -399,6 +419,21 @@ public static void PauseWaves() WaveManager.Waves.Clear(); } + /// + /// Pauses the specified list of respawn waves by iterating through each wave + /// and pausing it using the method. + /// + /// + /// A list of instances representing the waves to pause. + /// + public static void PauseWaves(List spawnableFactions) + { + foreach (SpawnableFaction spawnableFaction in spawnableFactions) + { + PauseWave(spawnableFaction); + } + } + /// /// Resumes respawn waves by filling WaveManager.Waves with values stored in . /// @@ -411,6 +446,29 @@ public static void ResumeWaves() PausedWaves.Clear(); } + /// + /// Restarts a specific respawn wave by adding it back to the active wave list + /// and removing it from the paused wave list if necessary. + /// + /// + /// The representing the wave to restart. + /// + public static void RestartWave(SpawnableFaction spawnableFaction) + { + if (TryGetWaveBase(spawnableFaction, out SpawnableWaveBase spawnableWaveBase)) + { + if (!WaveManager.Waves.Contains(spawnableWaveBase)) + { + WaveManager.Waves.Add(spawnableWaveBase); + } + + if (PausedWaves.Contains(spawnableWaveBase)) + { + PausedWaves.Remove(spawnableWaveBase); + } + } + } + /// /// Restarts respawn waves by clearing WaveManager.Waves and filling it with new values.. /// @@ -423,6 +481,21 @@ public static void RestartWaves() PausedWaves.Clear(); } + /// + /// Restarts the specified respawn waves by iterating through each wave + /// and restarting it using the method. + /// + /// + /// A list of instances representing the waves to restart. + /// + public static void RestartWaves(List spawnableFactions) + { + foreach (SpawnableFaction spawnableFaction in spawnableFactions) + { + RestartWave(spawnableFaction); + } + } + /// /// Tries to get the influence value of a given . /// diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index d1c0a12591..57ba6bcf85 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -8,12 +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; + using PlayerRoles.FirstPersonControl.Thirdperson; using PlayerRoles.Ragdolls; using PlayerRoles.Spectating; using PlayerRoles.Visibility; @@ -67,6 +66,15 @@ public RelativePosition ClientRelativePosition set => FirstPersonController.FpcModule.Motor.ReceivedPosition = value; } + /// + /// Gets or sets the associated with the player. + /// + public CharacterModel Model + { + get => FirstPersonController.FpcModule.CharacterModelInstance; + set => FirstPersonController.FpcModule.CharacterModelInstance = value; + } + /// /// Gets or sets the player's gravity. /// @@ -76,6 +84,15 @@ public Vector3 Gravity set => FirstPersonController.FpcModule.Motor.GravityController.Gravity = value; } + /// + /// Gets or sets the player's scale. + /// + public Vector3 Scale + { + get => FirstPersonController.FpcModule.Motor.ScaleController.Scale; + set => FirstPersonController.FpcModule.Motor.ScaleController.Scale = value; + } + /// /// Gets or sets a value indicating whether if the player should get damage. /// @@ -296,4 +313,4 @@ public void ResetStamina(bool multipliers = false) StaminaRegenMultiplier = 1f; } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 5de5dd5655..8cd65dc079 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -238,6 +238,15 @@ public static bool IsIdleModeEnabled /// public static void Restart() => Round.Restart(false, true, ServerStatic.NextRoundAction.Restart); + /// + /// Restarts the server with specified options. + /// + /// Indicates whether the restart should be fast. + /// Indicates whether to override the default restart action. + /// Specifies the action to perform after the restart. + public static void Restart(bool fastRestart, bool overrideRestartAction = false, ServerStatic.NextRoundAction restartAction = ServerStatic.NextRoundAction.DoNothing) => + Round.Restart(fastRestart, overrideRestartAction, restartAction); + /// /// Shutdowns the server, disconnects all players. /// @@ -266,6 +275,19 @@ public static bool RestartRedirect(ushort redirectPort) return true; } + /// + /// Redirects players to a server on another port, restarts the current server. + /// + /// The port to redirect players to. + /// Indicates whether the restart should be fast. + /// Indicates whether to override the default restart action. + /// Specifies the action to perform after the restart. + public static void RestartRedirect(ushort redirectPort, bool fastRestart, bool overrideRestartAction = false, ServerStatic.NextRoundAction restartAction = ServerStatic.NextRoundAction.DoNothing) + { + NetworkServer.SendToAll(new RoundRestartMessage(RoundRestartType.RedirectRestart, 0.0f, redirectPort, true, false)); + Timing.CallDelayed(0.5f, () => { Restart(fastRestart, overrideRestartAction, restartAction); }); + } + /// /// Redirects players to a server on another port, shutdowns the current server. /// @@ -280,6 +302,18 @@ public static bool ShutdownRedirect(ushort redirectPort) return true; } + /// + /// Redirects players to a server on another port, shutdowns the current server. + /// + /// The port to redirect players to. + /// Indicates whether to terminate the application after shutting down the server. + /// Indicates whether to suppress the broadcast notification about the shutdown. + public static void ShutdownRedirect(ushort redirectPort, bool quit, bool suppressShutdownBroadcast = false) + { + NetworkServer.SendToAll(new RoundRestartMessage(RoundRestartType.RedirectRestart, 0.0f, redirectPort, true, false)); + Timing.CallDelayed(0.5f, () => { Shutdown(quit, suppressShutdownBroadcast); }); + } + /// /// Executes a server command. /// @@ -307,4 +341,4 @@ public static bool TryGetSessionVariable(string key, out T result) return false; } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Toys/Capybara.cs b/EXILED/Exiled.API/Features/Toys/Capybara.cs index b88dbfd9c8..23e3e07f23 100644 --- a/EXILED/Exiled.API/Features/Toys/Capybara.cs +++ b/EXILED/Exiled.API/Features/Toys/Capybara.cs @@ -19,9 +19,9 @@ public class Capybara : AdminToy, IWrapper /// /// Initializes a new instance of the class. /// - /// The of the toy. - internal Capybara(CapybaraToy speakerToy) - : base(speakerToy, AdminToyType.Speaker) => Base = speakerToy; + /// The of the toy. + internal Capybara(CapybaraToy capybaraToy) + : base(capybaraToy, AdminToyType.Capybara) => Base = capybaraToy; /// /// Gets the prefab. diff --git a/EXILED/Exiled.API/Features/Warhead.cs b/EXILED/Exiled.API/Features/Warhead.cs index 97ce5a9cfe..f261577770 100644 --- a/EXILED/Exiled.API/Features/Warhead.cs +++ b/EXILED/Exiled.API/Features/Warhead.cs @@ -45,7 +45,11 @@ public static class Warhead /// /// Gets or sets a value indicating whether DeadmanSwitch detonation is enabled. /// - public static bool DeadmanSwitchEnabled { get; set; } = true; + public static bool DeadmanSwitchEnabled + { + get => DeadmanSwitch.IsDeadmanSwitchEnabled; + set => DeadmanSwitch.IsDeadmanSwitchEnabled = value; + } /// /// Gets or sets a value indicating whether automatic detonation is enabled. diff --git a/EXILED/Exiled.API/Features/Workstation.cs b/EXILED/Exiled.API/Features/Workstation.cs new file mode 100644 index 0000000000..7e7978a7ff --- /dev/null +++ b/EXILED/Exiled.API/Features/Workstation.cs @@ -0,0 +1,167 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Interfaces; + using InventorySystem.Items.Firearms.Attachments; + using Mirror; + using UnityEngine; + + /// + /// A wrapper class for . + /// + public class Workstation : IWrapper, IWorldSpace + { + /// + /// A dictionary mapping to . + /// + internal static readonly Dictionary WorkstationControllerToWorkstation = new(new ComponentsEqualityComparer()); + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + internal Workstation(WorkstationController workstationController) + { + WorkstationControllerToWorkstation.Add(workstationController, this); + Base = workstationController; + } + + /// + /// Gets a read-only collection of all instances. + /// + public static IReadOnlyCollection List => WorkstationControllerToWorkstation.Values; + + /// + /// Gets the underlying instance. + /// + public WorkstationController Base { get; } + + /// + /// Gets the of the workstation. + /// + public GameObject GameObject => Base.gameObject; + + /// + /// Gets the of the workstation. + /// + public Transform Transform => Base.transform; + + /// + /// Gets the the workstation is located in. + /// + public Room Room => Room.Get(Position); + + /// + /// Gets the of the workstation's room. + /// + public ZoneType Zone => Room.Zone; + + /// + /// Gets or sets the position of the workstation. + /// + public Vector3 Position + { + get => Transform.position; + set + { + NetworkServer.UnSpawn(GameObject); + Transform.position = value; + NetworkServer.Spawn(GameObject); + } + } + + /// + /// Gets or sets the rotation of the workstation. + /// + public Quaternion Rotation + { + get => Transform.rotation; + set + { + NetworkServer.UnSpawn(GameObject); + Transform.rotation = value; + NetworkServer.Spawn(GameObject); + } + } + + /// + /// Gets or sets the status of the workstation. + /// + public WorkstationController.WorkstationStatus Status + { + get => (WorkstationController.WorkstationStatus)Base.Status; + set => Base.NetworkStatus = (byte)value; + } + + /// + /// Gets the used by the workstation. + /// + public Stopwatch Stopwatch => Base.ServerStopwatch; + + /// + /// Gets or sets the player known to be using the workstation. + /// + public Player KnownUser + { + get => Player.Get(Base.KnownUser); + set => Base.KnownUser = value.ReferenceHub; + } + + /// + /// Gets a given a instance. + /// + /// The instance. + /// The instance. + public static Workstation Get(WorkstationController workstationController) => WorkstationControllerToWorkstation.TryGetValue(workstationController, out Workstation workstation) ? workstation : new(workstationController); + + /// + /// Gets all instances that match the specified predicate. + /// + /// The predicate to filter workstations. + /// An of matching workstations. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + + /// + /// Tries to get all instances that match the specified predicate. + /// + /// The predicate to filter workstations. + /// The matching workstations, if any. + /// true if any workstations were found; otherwise, false. + public static bool TryGet(Func predicate, out IEnumerable workstations) + { + workstations = Get(predicate); + return workstations.Any(); + } + + /// + /// Determines whether the specified player is in range of the workstation. + /// + /// The player to check. + /// true if the player is in range; otherwise, false. + public bool IsInRange(Player player) => Base.IsInRange(player.ReferenceHub); + + /// + /// Interacts with the workstation as the specified player. + /// + /// The player to interact as. + public void Interact(Player player) => Base.ServerInteract(player.ReferenceHub, Base.ActivateCollider.ColliderId); + + /// + /// Returns the Room in a human-readable format. + /// + /// A string containing Workstation-related data. + public override string ToString() => $"{GameObject.name} ({Zone}) [{Room}]"; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs b/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs index c2ec515742..ac3cc91781 100644 --- a/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs +++ b/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs @@ -17,7 +17,7 @@ namespace Exiled.CreditTags.Features public static class DatabaseHandler { - private const string Url = "https://raw.githubusercontent.com/ExSLMod-Team/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/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs index cf6d937b8b..fa39569e0a 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs @@ -937,7 +937,7 @@ protected virtual void ShowSelectedMessage(Player player) private void OnInternalOwnerChangingRole(ChangingRoleEventArgs ev) { - if (ev.Reason is SpawnReason.Escaped or SpawnReason.Destroyed) + if (ev.Reason is SpawnReason.Escaped or SpawnReason.Destroyed or SpawnReason.LateJoin) return; foreach (Item item in ev.Player.Items.ToList()) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs index d53846cbb0..c7a225a897 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs @@ -75,10 +75,9 @@ public override ItemType Type /// public override void Give(Player player, Item item, bool displayMessage = true) { - base.Give(player, item, displayMessage); - if (item.Is(out Keycard card)) SetupKeycard(card); + base.Give(player, item, displayMessage); } /// diff --git a/EXILED/Exiled.CustomRoles/API/Extensions.cs b/EXILED/Exiled.CustomRoles/API/Extensions.cs index a2d6bb25fd..8a0821216d 100644 --- a/EXILED/Exiled.CustomRoles/API/Extensions.cs +++ b/EXILED/Exiled.CustomRoles/API/Extensions.cs @@ -41,6 +41,25 @@ public static ReadOnlyCollection GetCustomRoles(this Player player) return roles.AsReadOnly(); } + /// + /// Checks whether the player has any custom role assigned. + /// + /// The to check. + /// true if the player has at least one custom role; otherwise, false. + public static bool HasAnyCustomRole(this Player player) + { + if (player == null) + return false; + + foreach (CustomRole role in CustomRole.Registered) + { + if (role.Check(player)) + return true; + } + + return false; + } + /// /// Registers an of s. /// @@ -105,4 +124,4 @@ public static void Unregister(this IEnumerable customRoles) /// The the has selected, or . public static ActiveAbility? GetSelectedAbility(this Player player) => !ActiveAbility.AllActiveAbilities.TryGetValue(player, out HashSet abilities) ? null : abilities.FirstOrDefault(a => a.Check(player, CheckType.Selected)); } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index ba6ecc95f8..db5e599cd3 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -176,6 +176,12 @@ public abstract class CustomRole /// public virtual string AbilityUsage { get; set; } = "Enter \".special\" in the console to use your ability. If you have multiple abilities, you can use this command to cycle through them, or specify the one to use with \".special ROLENAME AbilityNum\""; + /// + /// Gets or sets the number of players that naturally spawned with this custom role. + /// + [YamlIgnore] + public int SpawnedPlayers { get; set; } + /// /// Gets a by ID. /// @@ -506,27 +512,27 @@ public virtual void AddRole(Player player) { Log.Debug($"{Name}: Adding role to {player.Nickname}."); player.UniqueRole = Name; + TrackedPlayers.Add(player); if (Role != RoleTypeId.None) { if (KeepPositionOnSpawn) { if (KeepInventoryOnSpawn) - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None); + player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.None); else - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory); + player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.AssignInventory); } else { if (KeepInventoryOnSpawn && player.IsAlive) - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint); + player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.UseSpawnpoint); else - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All); + player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.All); } } player.UniqueRole = Name; - TrackedPlayers.Add(player); Timing.CallDelayed( AddRoleDelay, @@ -578,7 +584,9 @@ public virtual void AddRole(Player player) ability.AddAbility(player); } - ShowMessage(player); + if (CustomRoles.Instance!.Config.GotRoleHint.Show) + ShowMessage(player); + ShowBroadcast(player); RoleAdded(player); player.TryAddCustomRoleFriendlyFire(Name, CustomRoleFFMultiplier); @@ -814,48 +822,70 @@ protected bool TryAddItem(Player player, string itemName) protected Vector3 GetSpawnPosition() { if (SpawnProperties is null || SpawnProperties.Count() == 0) + { return Vector3.zero; + } + + float totalchance = 0f; + List<(float chance, Vector3 pos)> spawnPointPool = new(4); + + void Add(Vector3 pos, float chance) + { + if (chance <= 0f) + return; + + spawnPointPool.Add((chance, pos)); + totalchance += chance; + } - if (SpawnProperties.StaticSpawnPoints.Count > 0) + if (!SpawnProperties.StaticSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.StaticSpawnPoints) + foreach (StaticSpawnPoint sp in SpawnProperties.StaticSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.DynamicSpawnPoints.Count > 0) + if (!SpawnProperties.DynamicSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.DynamicSpawnPoints) + foreach (DynamicSpawnPoint sp in SpawnProperties.DynamicSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoleSpawnPoints.Count > 0) + if (!SpawnProperties.RoleSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoleSpawnPoints) + foreach (RoleSpawnPoint sp in SpawnProperties.RoleSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoomSpawnPoints.Count > 0) + if (!SpawnProperties.RoomSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoomSpawnPoints) + foreach (RoomSpawnPoint sp in SpawnProperties.RoomSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } + if (spawnPointPool.Count == 0 || totalchance <= 0f) + { + return Vector3.zero; + } + + float randomRoll = (float)(Loader.Random.NextDouble() * totalchance); + foreach ((float chance, Vector3 pos) in spawnPointPool) + { + if (randomRoll < chance) + { + return pos; + } + + randomRoll -= chance; + } + return Vector3.zero; } @@ -867,7 +897,6 @@ protected virtual void SubscribeEvents() Log.Debug($"{Name}: Loading events."); Exiled.Events.Handlers.Player.ChangingNickname += OnInternalChangingNickname; Exiled.Events.Handlers.Player.ChangingRole += OnInternalChangingRole; - Exiled.Events.Handlers.Player.Spawned += OnInternalSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll += OnSpawningRagdoll; Exiled.Events.Handlers.Player.Destroying += OnDestroying; } @@ -883,7 +912,6 @@ protected virtual void UnsubscribeEvents() Log.Debug($"{Name}: Unloading events."); Exiled.Events.Handlers.Player.ChangingNickname -= OnInternalChangingNickname; Exiled.Events.Handlers.Player.ChangingRole -= OnInternalChangingRole; - Exiled.Events.Handlers.Player.Spawned -= OnInternalSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll -= OnSpawningRagdoll; Exiled.Events.Handlers.Player.Destroying -= OnDestroying; } @@ -922,21 +950,15 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) ev.Player.CustomInfo = $"{ev.NewName}\n{CustomInfo}"; } - private void OnInternalSpawned(SpawnedEventArgs ev) - { - if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance) - AddRole(ev.Player); - } - private void OnInternalChangingRole(ChangingRoleEventArgs ev) { - if (ev.IsAllowed && ev.Reason != SpawnReason.Destroyed && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole))) + if (ev.IsAllowed && ev.Reason is not(SpawnReason.Destroyed or SpawnReason.CustomRole) && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole))) RemoveRole(ev.Player); } private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player)) + if (Check(ev.Player) && Role.IsFpcRole()) ev.Role = Role; } diff --git a/EXILED/Exiled.CustomRoles/CustomRoles.cs b/EXILED/Exiled.CustomRoles/CustomRoles.cs index 4ea4c77f1d..a66457b3bd 100644 --- a/EXILED/Exiled.CustomRoles/CustomRoles.cs +++ b/EXILED/Exiled.CustomRoles/CustomRoles.cs @@ -62,16 +62,24 @@ public override void OnEnabled() if (Config.UseKeypressActivation) keypressActivator = new(); + + Exiled.Events.Handlers.Player.Spawned += playerHandlers.OnSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll += playerHandlers.OnSpawningRagdoll; + + Exiled.Events.Handlers.Server.WaitingForPlayers += playerHandlers.OnWaitingForPlayers; base.OnEnabled(); } /// public override void OnDisabled() { + Exiled.Events.Handlers.Player.Spawned -= playerHandlers!.OnSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll -= playerHandlers!.OnSpawningRagdoll; + + Exiled.Events.Handlers.Server.WaitingForPlayers -= playerHandlers!.OnWaitingForPlayers; + keypressActivator = null; base.OnDisabled(); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs index b53556f007..1a10e38dcf 100644 --- a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs +++ b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs @@ -7,6 +7,14 @@ namespace Exiled.CustomRoles.Events { + using System; + using System.Collections.Generic; + using System.Threading; + + using Exiled.API.Enums; + using Exiled.API.Features; + using Exiled.CustomRoles.API; + using Exiled.CustomRoles.API.Features; using Exiled.Events.EventArgs.Player; /// @@ -14,6 +22,16 @@ namespace Exiled.CustomRoles.Events /// public class PlayerHandlers { + private static readonly HashSet ValidSpawnReasons = new() + { + SpawnReason.RoundStart, + SpawnReason.Respawn, + SpawnReason.LateJoin, + SpawnReason.Revived, + SpawnReason.Escaped, + SpawnReason.ItemUsage, + }; + private readonly CustomRoles plugin; /// @@ -25,6 +43,15 @@ public PlayerHandlers(CustomRoles plugin) this.plugin = plugin; } + /// + internal void OnWaitingForPlayers() + { + foreach (CustomRole role in CustomRole.Registered) + { + role.SpawnedPlayers = 0; + } + } + /// internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { @@ -34,5 +61,66 @@ internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) plugin.StopRagdollPlayers.Remove(ev.Player); } } + + /// + internal void OnSpawned(SpawnedEventArgs ev) + { + if (!ValidSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) + { + return; + } + + float totalChance = 0f; + List eligibleRoles = new(8); + + foreach (CustomRole role in CustomRole.Registered) + { + if (role.Role == ev.Player.Role.Type && !role.IgnoreSpawnSystem && role.SpawnChance > 0 && !role.Check(ev.Player) && (role.SpawnProperties is null || role.SpawnedPlayers < role.SpawnProperties.Limit)) + { + eligibleRoles.Add(role); + totalChance += role.SpawnChance; + } + } + + if (eligibleRoles.Count == 0) + { + return; + } + + float lotterySize = Math.Max(100f, totalChance); + float randomRoll = (float)Loader.Loader.Random.NextDouble() * lotterySize; + + if (randomRoll >= totalChance) + { + return; + } + + foreach (CustomRole candidateRole in eligibleRoles) + { + if (randomRoll >= candidateRole.SpawnChance) + { + randomRoll -= candidateRole.SpawnChance; + continue; + } + + if (candidateRole.SpawnProperties is null) + { + candidateRole.AddRole(ev.Player); + break; + } + + int newSpawnCount = candidateRole.SpawnedPlayers++; + if (newSpawnCount <= candidateRole.SpawnProperties.Limit) + { + candidateRole.AddRole(ev.Player); + break; + } + else + { + candidateRole.SpawnedPlayers--; + randomRoll -= candidateRole.SpawnChance; + } + } + } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs index 6bb6c9ad6c..98f2588187 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/ChargingJailbirdEventArgs.cs @@ -12,20 +12,20 @@ namespace Exiled.Events.EventArgs.Item using Exiled.Events.EventArgs.Interfaces; /// - /// Contains all information before a player charges a . + /// Contains all information before a player starts charging an . /// - public class ChargingJailbirdEventArgs : IItemEvent + public class ChargingJailbirdEventArgs : IItemEvent, IDeniableEvent { /// /// Initializes a new instance of the class. /// - /// - /// The item being charged. - /// Whether the item can be charged. - public ChargingJailbirdEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase swingItem, bool isAllowed = true) + /// The player who is attempting to charge the Jailbird. + /// The jailbird being charged. + /// Whether the item is allowed to be charged. + public ChargingJailbirdEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, bool isAllowed = true) { Player = Player.Get(player); - Jailbird = (Jailbird)Item.Get(swingItem); + Jailbird = Item.Get(jailbird); IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Item/InspectedItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/InspectedItemEventArgs.cs new file mode 100644 index 0000000000..283fd4910f --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/InspectedItemEventArgs.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Item +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items; + + /// + /// Contains all information before weapon is inspected. + /// + public class InspectedItemEventArgs : IItemEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + public InspectedItemEventArgs(ItemBase item) + { + Item = Item.Get(item); + } + + /// + public Player Player => Item.Owner; + + /// + public Item Item { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Item/InspectingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/InspectingItemEventArgs.cs new file mode 100644 index 0000000000..5bb7cb4077 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/InspectingItemEventArgs.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Item +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items; + + /// + /// Contains all information before weapon is inspected. + /// + public class InspectingItemEventArgs : IItemEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public InspectingItemEventArgs(ItemBase item, bool isAllowed = true) + { + Item = Item.Get(item); + IsAllowed = isAllowed; + } + + /// + public Player Player => Item.Owner; + + /// + public Item Item { get; } + + /// + /// Setter will not work if inspected is a or a . + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChargeCompleteEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChargeCompleteEventArgs.cs new file mode 100644 index 0000000000..d583f22b3f --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChargeCompleteEventArgs.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Item +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information when a player completes charging a . + /// + public class JailbirdChargeCompleteEventArgs : IItemEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who completed the charge. + /// The Jailbird item whose charge is complete. + /// Whether the Jailbird is allowed to attack after charging. + public JailbirdChargeCompleteEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, bool isAllowed = true) + { + Player = Player.Get(player); + Jailbird = Item.Get(jailbird); + IsAllowed = isAllowed; + } + + /// + /// Gets the who completed the charge. + /// + public Player Player { get; } + + /// + /// Gets the that was fully charged. + /// + public Jailbird Jailbird { get; } + + /// + /// Gets the associated with the charged Jailbird. + /// + public Item Item => Jailbird; + + /// + /// Gets or sets a value indicating whether the Jailbird is allowed to attack after charging. + /// + public bool IsAllowed { get; set; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs new file mode 100644 index 0000000000..5df04b729e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information after a player's Aspect Ratio changes. + /// + public class ChangedRatioEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who is changed ratio. + /// + /// + /// Old aspect ratio of the player. + /// + /// + /// New aspect ratio of the player. + /// + /// + public ChangedRatioEventArgs(ReferenceHub player, float oldratio, float newratio) + { + Player = Player.Get(player); + OldRatio = oldratio.GetAspectRatioLabel(); + NewRatio = newratio.GetAspectRatioLabel(); + } + + /// + /// Gets the player who is changed ratio. + /// + public Player Player { get; } + + /// + /// Gets the players old ratio. + /// + public AspectRatioType OldRatio { get; } + + /// + /// Gets the players new ratio. + /// + public AspectRatioType NewRatio { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/DiedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DiedEventArgs.cs index 26b6a26312..24e91e6870 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DiedEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DiedEventArgs.cs @@ -13,6 +13,7 @@ namespace Exiled.Events.EventArgs.Player using Interfaces; using PlayerRoles; + using PlayerRoles.Ragdolls; using CustomAttackerHandler = API.Features.DamageHandlers.AttackerDamageHandler; using DamageHandlerBase = PlayerStatsSystem.DamageHandlerBase; @@ -20,7 +21,7 @@ namespace Exiled.Events.EventArgs.Player /// /// Contains all information after a player dies. /// - public class DiedEventArgs : IPlayerEvent, IAttackerEvent + public class DiedEventArgs : IPlayerEvent, IAttackerEvent, IRagdollEvent { /// /// Initializes a new instance of the class. @@ -32,12 +33,16 @@ public class DiedEventArgs : IPlayerEvent, IAttackerEvent /// /// /// - public DiedEventArgs(Player target, RoleTypeId targetOldRole, DamageHandlerBase damageHandler) + /// + /// + /// + public DiedEventArgs(Player target, RoleTypeId targetOldRole, DamageHandlerBase damageHandler, BasicRagdoll ragdoll) { DamageHandler = new CustomDamageHandler(target, damageHandler); Attacker = DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler) ? attackerDamageHandler.Attacker : null; Player = target; TargetOldRole = targetOldRole; + Ragdoll = Ragdoll.Get(ragdoll); } /// @@ -59,5 +64,10 @@ public DiedEventArgs(Player target, RoleTypeId targetOldRole, DamageHandlerBase /// Gets the attacker. /// public Player Attacker { get; } + + /// + /// Gets ragdoll of the dead player. + /// + public Ragdoll Ragdoll { get; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 4a4fa0dcba..9e80978aa2 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs @@ -39,7 +39,7 @@ public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeSc Player = Player.Get(referenceHub); NewRole = newRole; EscapeScenario = escapeScenario; - IsAllowed = escapeScenario is not EscapeScenario.None and not EscapeScenario.CustomEscape; + IsAllowed = escapeScenario is not EscapeScenario.None; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs new file mode 100644 index 0000000000..57f17a3eda --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.Events.EventArgs.Player +{ + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + using MapGeneration; + + /// + /// Contains the information when a player changes rooms. + /// + public class RoomChangedEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player whose room has changed. + /// The room identifier before the change (Can be null on round start). + /// The room identifier after the change. + public RoomChangedEventArgs(ReferenceHub player, RoomIdentifier oldRoom, RoomIdentifier newRoom) + { + Player = Player.Get(player); + OldRoom = Room.Get(oldRoom); + NewRoom = Room.Get(newRoom); + } + + /// + public Player Player { get; } + + /// + /// Gets the previous room the player was in. + /// + public Room OldRoom { get; } + + /// + /// Gets the new room the player entered. + /// + public Room NewRoom { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Scp049/FinishingSenseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp049/FinishingSenseEventArgs.cs new file mode 100644 index 0000000000..43baf5987b --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp049/FinishingSenseEventArgs.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp049 +{ + using Exiled.API.Features; + using Exiled.API.Features.Roles; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information before SCP-049 finishes his sense ability. + /// + public class FinishingSenseEventArgs : IScp049Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The SCP-049 instance triggering the event. + /// + /// + /// The player targeted by SCP-049's Sense ability. + /// + /// + /// The time in seconds before the Sense ability can be used again. + /// + /// + /// Specifies whether the Sense effect is allowed to finish. + /// + /// + public FinishingSenseEventArgs(ReferenceHub scp049, ReferenceHub target, double cooldowntime, bool isAllowed = true) + { + Player = Player.Get(scp049); + Scp049 = Player.Role.As(); + Target = Player.Get(target); + IsAllowed = isAllowed; + CooldownTime = cooldowntime; + } + + /// + public Scp049Role Scp049 { get; } + + /// + /// Gets the player who is controlling SCP-049. + /// + public Player Player { get; } + + /// + /// Gets the player who is SCP-049's active target. Can be null. + /// + public Player Target { get; } + + /// + /// Gets or sets the cooldown duration of the Sense ability. + /// + public double CooldownTime { get; set; } + + /// + /// Gets or sets a value indicating whether the server will finishing or not finishing 049 Sense Ability. + /// + public bool IsAllowed { get; set; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs new file mode 100644 index 0000000000..1dc1934942 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs @@ -0,0 +1,59 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp096 +{ + using API.Features; + + using Interfaces; + + using Scp096Role = API.Features.Roles.Scp096Role; + + /// + /// Contains all information after removing a target from SCP-096. + /// + public class RemovingTargetEventArgs : IScp096Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public RemovingTargetEventArgs(Player scp096, Player target, bool isAllowed = true) + { + Player = scp096; + Scp096 = scp096.Role.As(); + Target = target; + IsAllowed = isAllowed; + } + + /// + /// Gets the that is controlling SCP-096. + /// + public Player Player { get; } + + /// + public Scp096Role Scp096 { get; } + + /// + /// Gets the being removed as a target. + /// + public Player Target { get; } + + /// + /// Gets or sets a value indicating whether the target is allowed to be removed. + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs new file mode 100644 index 0000000000..4bd590dc55 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs @@ -0,0 +1,65 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp914 +{ + using API.Features; + using API.Features.Items; + using global::Scp914; + using Interfaces; + using InventorySystem.Items; + using InventorySystem.Items.Pickups; + + /// + /// Contains all information before SCP-914 upgrades an item. + /// + public class UpgradedInventoryItemEventArgs : IItemEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UpgradedInventoryItemEventArgs(Player player, ItemBase item, Scp914KnobSetting knobSetting, ItemBase[] result) + { + Player = player; + Item = Item.Get(item); + KnobSetting = knobSetting; + Result = result; + } + + /// + /// Gets SCP-914 working knob setting. + /// + public Scp914KnobSetting KnobSetting { get; } + + /// + /// Gets a list of items to be upgraded inside SCP-914. + /// + public Item Item { get; } + + /// + /// Gets the who owns the item to be upgraded. + /// + public Player Player { get; } + + /// + /// Gets the array of items created as a result of SCP-914 upgraded. + /// + public ItemBase[] Result { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs new file mode 100644 index 0000000000..5bd2c9d952 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp914 +{ + using Exiled.API.Features.Pickups; + using Exiled.Events.EventArgs.Interfaces; + using global::Scp914; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Contains all information before SCP-914 upgrades an item. + /// + public class UpgradedPickupEventArgs : IPickupEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UpgradedPickupEventArgs(ItemPickupBase item, Vector3 newPos, Scp914KnobSetting knobSetting, ItemPickupBase[] result) + { + Pickup = Pickup.Get(item); + OutputPosition = newPos; + KnobSetting = knobSetting; + Result = result; + } + + /// + /// Gets a list of items to be upgraded inside SCP-914. + /// + public Pickup Pickup { get; } + + /// + /// Gets the position the item will be output to. + /// + public Vector3 OutputPosition { get; } + + /// + /// Gets SCP-914 working knob setting. + /// + public Scp914KnobSetting KnobSetting { get; } + + /// + /// Gets the array of items created as a result of SCP-914 upgraded. + /// + public ItemPickupBase[] Result { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp939/PlacingMimicPointEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp939/PlacingMimicPointEventArgs.cs new file mode 100644 index 0000000000..a761942caf --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp939/PlacingMimicPointEventArgs.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp939 +{ + using Exiled.API.Features; + using Exiled.API.Features.Roles; + using Exiled.Events.EventArgs.Interfaces; + using RelativePositioning; + + /// + /// Contains all information before mimicry point is placed. + /// + public class PlacingMimicPointEventArgs : IScp939Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public PlacingMimicPointEventArgs(Player player, RelativePosition position, bool isAllowed = true) + { + Player = player; + Scp939 = player.Role.As(); + Position = position; + IsAllowed = isAllowed; + } + + /// + public Player Player { get; } + + /// + public Scp939Role Scp939 { get; } + + /// + public bool IsAllowed { get; set; } + + /// + /// Gets or sets a position of mimicry point. + /// + public RelativePosition Position { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 103ba58829..b2c8e47e19 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -68,10 +68,12 @@ public override void OnEnabled() Handlers.Server.RestartingRound += Handlers.Internal.Round.OnRestartingRound; Handlers.Server.RoundStarted += Handlers.Internal.Round.OnRoundStarted; Handlers.Player.ChangingRole += Handlers.Internal.Round.OnChangingRole; + Handlers.Player.SpawningRagdoll += Handlers.Internal.Round.OnSpawningRagdoll; Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense; Handlers.Player.Verified += Handlers.Internal.Round.OnVerified; Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; + RoleAssigner.OnPlayersSpawned += Handlers.Server.OnAllPlayersSpawned; CharacterClassManager.OnRoundStarted += Handlers.Server.OnRoundStarted; WaveManager.OnWaveSpawned += Handlers.Server.OnRespawnedTeam; InventorySystem.InventoryExtensions.OnItemAdded += Handlers.Player.OnItemAdded; @@ -105,12 +107,13 @@ public override void OnDisabled() Handlers.Server.RestartingRound -= Handlers.Internal.Round.OnRestartingRound; Handlers.Server.RoundStarted -= Handlers.Internal.Round.OnRoundStarted; Handlers.Player.ChangingRole -= Handlers.Internal.Round.OnChangingRole; + Handlers.Player.SpawningRagdoll -= Handlers.Internal.Round.OnSpawningRagdoll; Handlers.Scp049.ActivatingSense -= Handlers.Internal.Round.OnActivatingSense; Handlers.Player.Verified -= Handlers.Internal.Round.OnVerified; Handlers.Map.ChangedIntoGrenade -= Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; CharacterClassManager.OnRoundStarted -= Handlers.Server.OnRoundStarted; - + RoleAssigner.OnPlayersSpawned -= Handlers.Server.OnAllPlayersSpawned; InventorySystem.InventoryExtensions.OnItemAdded -= Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved -= Handlers.Player.OnItemRemoved; WaveManager.OnWaveSpawned -= Handlers.Server.OnRespawnedTeam; @@ -161,4 +164,4 @@ public void Unpatch() Log.Debug("All events have been unpatched complete. Goodbye!"); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index 9885747c7e..88b22843f0 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -9,7 +9,6 @@ namespace Exiled.Events.Features { using System; using System.Collections.Generic; - using System.Linq; using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; @@ -31,8 +30,20 @@ namespace Exiled.Events.Features /// public class Event : IExiledEvent { + private record Registration(CustomEventHandler handler, int priority); + + private record AsyncRegistration(CustomAsyncEventHandler handler, int priority); + private static readonly List EventsValue = new(); + private static readonly IComparer RegisterComparable = Comparer.Create((x, y) => y.priority - x.priority); + + private static readonly IComparer AsyncRegisterComparable = Comparer.Create((x, y) => y.priority - x.priority); + + private readonly List innerEvent = new(); + + private readonly List innerAsyncEvent = new(); + private bool patched; /// @@ -43,10 +54,6 @@ public Event() EventsValue.Add(this); } - private event CustomEventHandler InnerEvent; - - private event CustomAsyncEventHandler InnerAsyncEvent; - /// /// Gets a of which contains all the instances. /// @@ -105,6 +112,14 @@ public Event() /// /// The handler to add. public void Subscribe(CustomEventHandler handler) + => Subscribe(handler, 0); + + /// + /// Subscribes a target to the inner event if the conditional is true. + /// + /// The handler to add. + /// The highest priority is the first called, the lowest the last. + public void Subscribe(CustomEventHandler handler, int priority) { Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!"); @@ -114,7 +129,21 @@ public void Subscribe(CustomEventHandler handler) patched = true; } - InnerEvent += handler; + if (handler == null) + return; + + Registration registration = new Registration(handler, priority); + int index = innerEvent.BinarySearch(registration, RegisterComparable); + if (index < 0) + { + innerEvent.Insert(~index, registration); + } + else + { + while (index < innerEvent.Count && innerEvent[index].priority == priority) + index++; + innerEvent.Insert(index, registration); + } } /// @@ -122,6 +151,14 @@ public void Subscribe(CustomEventHandler handler) /// /// The handler to add. public void Subscribe(CustomAsyncEventHandler handler) + => Subscribe(handler, 0); + + /// + /// Subscribes a target to the inner event if the conditional is true. + /// + /// The handler to add. + /// The highest priority is the first called, the lowest the last. + public void Subscribe(CustomAsyncEventHandler handler, int priority) { Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!"); @@ -131,7 +168,21 @@ public void Subscribe(CustomAsyncEventHandler handler) patched = true; } - InnerAsyncEvent += handler; + if (handler == null) + return; + + AsyncRegistration registration = new AsyncRegistration(handler, 0); + int index = innerAsyncEvent.BinarySearch(registration, AsyncRegisterComparable); + if (index < 0) + { + innerAsyncEvent.Insert(~index, registration); + } + else + { + while (index < innerAsyncEvent.Count && innerAsyncEvent[index].priority == priority) + index++; + innerAsyncEvent.Insert(index, registration); + } } /// @@ -140,7 +191,9 @@ public void Subscribe(CustomAsyncEventHandler handler) /// The handler to add. public void Unsubscribe(CustomEventHandler handler) { - InnerEvent -= handler; + int index = innerEvent.FindIndex(p => p.handler == handler); + if (index != -1) + innerEvent.RemoveAt(index); } /// @@ -149,7 +202,9 @@ public void Unsubscribe(CustomEventHandler handler) /// The handler to add. public void Unsubscribe(CustomAsyncEventHandler handler) { - InnerAsyncEvent -= handler; + int index = innerAsyncEvent.FindIndex(p => p.handler == handler); + if (index != -1) + innerAsyncEvent.RemoveAt(index); } /// @@ -157,25 +212,61 @@ public void Unsubscribe(CustomAsyncEventHandler handler) /// public void InvokeSafely() { - InvokeNormal(); - InvokeAsync(); + BlendedInvoke(); } /// - internal void InvokeNormal() + internal void BlendedInvoke() { - if (InnerEvent is null) - return; + Registration[] innerEvent = this.innerEvent.ToArray(); + AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); + int count = innerEvent.Length + innerAsyncEvent.Length; + int eventIndex = 0, asyncEventIndex = 0; - foreach (CustomEventHandler handler in InnerEvent.GetInvocationList().Cast()) + for (int i = 0; i < count; i++) + { + if (eventIndex < innerEvent.Length && (asyncEventIndex >= innerAsyncEvent.Length || innerEvent[eventIndex].priority >= innerAsyncEvent[asyncEventIndex].priority)) + { + try + { + innerEvent[eventIndex].handler(); + } + catch (Exception ex) + { + Log.Error($"Method \"{innerEvent[eventIndex].handler.Method.Name}\" of the class \"{innerEvent[eventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + } + + eventIndex++; + } + else + { + try + { + Timing.RunCoroutine(innerAsyncEvent[asyncEventIndex].handler()); + } + catch (Exception ex) + { + Log.Error($"Method \"{innerAsyncEvent[asyncEventIndex].handler.Method.Name}\" of the class \"{innerAsyncEvent[asyncEventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + } + + asyncEventIndex++; + } + } + } + + /// + internal void InvokeNormal() + { + Registration[] innerEvent = this.innerEvent.ToArray(); + foreach (Registration registration in innerEvent) { try { - handler(); + registration.handler(); } catch (Exception ex) { - Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } @@ -183,18 +274,16 @@ internal void InvokeNormal() /// internal void InvokeAsync() { - if (InnerAsyncEvent is null) - return; - - foreach (CustomAsyncEventHandler handler in InnerAsyncEvent.GetInvocationList().Cast()) + AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); + foreach (AsyncRegistration registration in innerAsyncEvent) { try { - Timing.RunCoroutine(handler()); + Timing.RunCoroutine(registration.handler()); } catch (Exception ex) { - Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } diff --git a/EXILED/Exiled.Events/Features/Event{T}.cs b/EXILED/Exiled.Events/Features/Event{T}.cs index e9b6e546b9..2d6252b91e 100644 --- a/EXILED/Exiled.Events/Features/Event{T}.cs +++ b/EXILED/Exiled.Events/Features/Event{T}.cs @@ -36,8 +36,20 @@ namespace Exiled.Events.Features /// The specified that the event will use. public class Event : IExiledEvent { + private record Registration(CustomEventHandler handler, int priority); + + private record AsyncRegistration(CustomAsyncEventHandler handler, int priority); + private static readonly Dictionary> TypeToEvent = new(); + private static readonly IComparer RegisterComparable = Comparer.Create((x, y) => y.priority - x.priority); + + private static readonly IComparer AsyncRegisterComparable = Comparer.Create((x, y) => y.priority - x.priority); + + private readonly List innerEvent = new(); + + private readonly List innerAsyncEvent = new(); + private bool patched; /// @@ -48,10 +60,6 @@ public Event() TypeToEvent.Add(typeof(T), this); } - private event CustomEventHandler InnerEvent; - - private event CustomAsyncEventHandler InnerAsyncEvent; - /// /// Gets a of which contains all the instances. /// @@ -110,6 +118,14 @@ public Event() /// /// The handler to add. public void Subscribe(CustomEventHandler handler) + => Subscribe(handler, 0); + + /// + /// Subscribes a target to the inner event if the conditional is true. + /// + /// The handler to add. + /// The highest priority is the first called, the lowest the last. + public void Subscribe(CustomEventHandler handler, int priority) { Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!"); @@ -119,7 +135,21 @@ public void Subscribe(CustomEventHandler handler) patched = true; } - InnerEvent += handler; + if (handler == null) + return; + + Registration registration = new Registration(handler, priority); + int index = innerEvent.BinarySearch(registration, RegisterComparable); + if (index < 0) + { + innerEvent.Insert(~index, registration); + } + else + { + while (index < innerEvent.Count && innerEvent[index].priority == priority) + index++; + innerEvent.Insert(index, registration); + } } /// @@ -127,6 +157,14 @@ public void Subscribe(CustomEventHandler handler) /// /// The handler to add. public void Subscribe(CustomAsyncEventHandler handler) + => Subscribe(handler, 0); + + /// + /// Subscribes a target to the inner event if the conditional is true. + /// + /// The handler to add. + /// The highest priority is the first called, the lowest the last. + public void Subscribe(CustomAsyncEventHandler handler, int priority) { Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!"); @@ -136,7 +174,21 @@ public void Subscribe(CustomAsyncEventHandler handler) patched = true; } - InnerAsyncEvent += handler; + if (handler == null) + return; + + AsyncRegistration registration = new AsyncRegistration(handler, 0); + int index = innerAsyncEvent.BinarySearch(registration, AsyncRegisterComparable); + if (index < 0) + { + innerAsyncEvent.Insert(~index, registration); + } + else + { + while (index < innerAsyncEvent.Count && innerAsyncEvent[index].priority == priority) + index++; + innerAsyncEvent.Insert(index, registration); + } } /// @@ -145,7 +197,9 @@ public void Subscribe(CustomAsyncEventHandler handler) /// The handler to add. public void Unsubscribe(CustomEventHandler handler) { - InnerEvent -= handler; + int index = innerEvent.FindIndex(p => p.handler == handler); + if (index != -1) + innerEvent.RemoveAt(index); } /// @@ -154,7 +208,9 @@ public void Unsubscribe(CustomEventHandler handler) /// The handler to add. public void Unsubscribe(CustomAsyncEventHandler handler) { - InnerAsyncEvent -= handler; + int index = innerAsyncEvent.FindIndex(p => p.handler == handler); + if (index != -1) + innerAsyncEvent.RemoveAt(index); } /// @@ -164,25 +220,61 @@ public void Unsubscribe(CustomAsyncEventHandler handler) /// Event or its arg is . public void InvokeSafely(T arg) { - InvokeNormal(arg); - InvokeAsync(arg); + BlendedInvoke(arg); } /// - internal void InvokeNormal(T arg) + internal void BlendedInvoke(T arg) { - if (InnerEvent is null) - return; + Registration[] innerEvent = this.innerEvent.ToArray(); + AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); + int count = innerEvent.Length + innerAsyncEvent.Length; + int eventIndex = 0, asyncEventIndex = 0; - foreach (CustomEventHandler handler in InnerEvent.GetInvocationList().Cast>()) + for (int i = 0; i < count; i++) + { + if (eventIndex < innerEvent.Length && (asyncEventIndex >= innerAsyncEvent.Length || innerEvent[eventIndex].priority >= innerAsyncEvent[asyncEventIndex].priority)) + { + try + { + innerEvent[eventIndex].handler(arg); + } + catch (Exception ex) + { + Log.Error($"Method \"{innerEvent[eventIndex].handler.Method.Name}\" of the class \"{innerEvent[eventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + } + + eventIndex++; + } + else + { + try + { + Timing.RunCoroutine(innerAsyncEvent[asyncEventIndex].handler(arg)); + } + catch (Exception ex) + { + Log.Error($"Method \"{innerAsyncEvent[asyncEventIndex].handler.Method.Name}\" of the class \"{innerAsyncEvent[asyncEventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + } + + asyncEventIndex++; + } + } + } + + /// + internal void InvokeNormal(T arg) + { + Registration[] innerEvent = this.innerEvent.ToArray(); + foreach (Registration registration in innerEvent) { try { - handler(arg); + registration.handler(arg); } catch (Exception ex) { - Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } @@ -190,18 +282,16 @@ internal void InvokeNormal(T arg) /// internal void InvokeAsync(T arg) { - if (InnerAsyncEvent is null) - return; - - foreach (CustomAsyncEventHandler handler in InnerAsyncEvent.GetInvocationList().Cast>()) + AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); + foreach (AsyncRegistration registration in innerAsyncEvent) { try { - Timing.RunCoroutine(handler(arg)); + Timing.RunCoroutine(registration.handler(arg)); } catch (Exception ex) { - Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); + Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } diff --git a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs index 8115dc4e65..46e1f5881d 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs @@ -29,42 +29,43 @@ public static void OnClientStarted() { PrefabHelper.Prefabs.Clear(); - Dictionary prefabs = new(); + Dictionary prefabs = new(); foreach (KeyValuePair prefab in NetworkClient.prefabs) { - if (!prefabs.ContainsKey(prefab.Key)) - prefabs.Add(prefab.Key, prefab.Value); + if(!prefabs.ContainsKey(prefab.Key) && prefab.Value.TryGetComponent(out Component component)) + prefabs.Add(prefab.Key, (prefab.Value, component)); } foreach (NetworkIdentity ragdollPrefab in RagdollManager.AllRagdollPrefabs) { - if (!prefabs.ContainsKey(ragdollPrefab.assetId)) - prefabs.Add(ragdollPrefab.assetId, ragdollPrefab.gameObject); + if(!prefabs.ContainsKey(ragdollPrefab.assetId) && ragdollPrefab.gameObject.TryGetComponent(out Component component)) + prefabs.Add(ragdollPrefab.assetId, (ragdollPrefab.gameObject, component)); } for (int i = 0; i < EnumUtils.Values.Length; i++) { PrefabType prefabType = EnumUtils.Values[i]; PrefabAttribute attribute = prefabType.GetPrefabAttribute(); - if (prefabs.TryGetValue(attribute.AssetId, out GameObject gameObject)) + if (prefabs.TryGetValue(attribute.AssetId, out (GameObject, Component) tuple)) { - PrefabHelper.Prefabs.Add(prefabType, gameObject); + GameObject gameObject = tuple.Item1; + PrefabHelper.Prefabs.Add(prefabType, prefabs.FirstOrDefault(prefab => prefab.Key == attribute.AssetId || prefab.Value.Item1.name.Contains(attribute.Name)).Value); prefabs.Remove(attribute.AssetId); continue; } - KeyValuePair? value = prefabs.FirstOrDefault(x => x.Value.name == attribute.Name); + KeyValuePair? value = prefabs.FirstOrDefault(x => x.Value.Item1.name == attribute.Name); if (value.HasValue) { - PrefabHelper.Prefabs.Add(prefabType, gameObject); + PrefabHelper.Prefabs.Add(prefabType, prefabs.FirstOrDefault(prefab => prefab.Key == attribute.AssetId || prefab.Value.Item1.name.Contains(attribute.Name)).Value); prefabs.Remove(value.Value.Key); continue; } } - foreach (KeyValuePair missing in prefabs) - Log.Warn($"Missing prefab in {nameof(PrefabType)}: {missing.Value.name} ({missing.Key})"); + foreach (KeyValuePair missing in prefabs) + Log.Warn($"Missing prefab in {nameof(PrefabType)}: {missing.Value.Item1.name} ({missing.Key})"); } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 99212af062..3ce8660bbe 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -29,7 +29,10 @@ namespace Exiled.Events.Handlers.Internal using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp244.Hypothermia; using PlayerRoles; + using PlayerRoles.FirstPersonControl; using PlayerRoles.RoleAssign; + using UnityEngine; + using Utils.Networking; using Utils.NonAllocLINQ; /// @@ -82,6 +85,13 @@ public static void OnChangingRole(ChangingRoleEventArgs ev) ev.Player.Inventory.ServerDropEverything(); } + /// + public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) + { + if (ev.Role.IsDead() || !ev.Role.IsFpcRole()) + ev.IsAllowed = false; + } + /// public static void OnActivatingSense(ActivatingSenseEventArgs ev) { @@ -107,10 +117,10 @@ public static void OnVerified(VerifiedEventArgs ev) ev.Player.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkLightsEnabled), false); } - // TODO: Remove if this has been fixed for https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/947 - if (ev.Player.TryGetEffect(out Hypothermia hypothermia)) + // Fix bug that player that Join do not receive information about other players Scale + foreach (Player player in ReferenceHub.AllHubs.Select(Player.Get)) { - hypothermia.SubEffects = hypothermia.SubEffects.Where(x => x.GetType() != typeof(PostProcessSubEffect)).ToArray(); + player.SetFakeScale(player.Scale, new List() { ev.Player }); } } diff --git a/EXILED/Exiled.Events/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index d8153a586e..4020038448 100644 --- a/EXILED/Exiled.Events/Handlers/Item.cs +++ b/EXILED/Exiled.Events/Handlers/Item.cs @@ -44,10 +44,15 @@ public static class Item public static Event Swinging { get; set; } = new(); /// - /// Invoked before a is charged. + /// Invoked when a starts charging. /// public static Event ChargingJailbird { get; set; } = new(); + /// + /// Invoked after a finishes charging. + /// + public static Event JailbirdChargeComplete { get; set; } = new(); + /// /// Invoked before a radio pickup is draining battery. /// @@ -58,6 +63,16 @@ public static class Item /// public static Event ChangingMicroHIDPickupState { get; set; } = new(); + /// + /// Invoked before item inspection is started. + /// + public static Event InspectingItem { get; set; } = new(); + + /// + /// Invoked after item inspection is started. + /// + public static Event InspectedItem { get; set; } = new(); + /// /// Invoked before a firing while on the ground. /// The client will still see all effects, like sounds and shoot. @@ -102,11 +117,17 @@ public static class Item public static void OnSwinging(SwingingEventArgs ev) => Swinging.InvokeSafely(ev); /// - /// Called before a is charged. + /// Called before a that is being charged. /// /// The instance. public static void OnChargingJailbird(ChargingJailbirdEventArgs ev) => ChargingJailbird.InvokeSafely(ev); + /// + /// Called after a finish charging. + /// + /// The instance. + public static void OnJailbirdChargeComplete(JailbirdChargeCompleteEventArgs ev) => JailbirdChargeComplete.InvokeSafely(ev); + /// /// Called before radio pickup is draining battery. /// @@ -118,5 +139,17 @@ public static class Item /// /// The instance. public static void OnChangingMicroHIDPickupState(ChangingMicroHIDPickupStateEventArgs ev) => ChangingMicroHIDPickupState.InvokeSafely(ev); + + /// + /// Called before item inspection is started. + /// + /// The instance. + public static void OnInspectingItem(InspectingItemEventArgs ev) => InspectingItem.InvokeSafely(ev); + + /// + /// Called before item inspection is started. + /// + /// The instance. + public static void OnInspectedItem(InspectedItemEventArgs ev) => InspectedItem.InvokeSafely(ev); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e35df7b044..7037747e7e 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -103,6 +103,11 @@ public class Player /// public static Event CancelledItemUse { get; set; } = new(); + /// + /// Invoked after a 's aspect ratio has changed. + /// + public static Event ChangedRatio { get; set; } = new(); + /// /// Invoked after a interacted with something. /// @@ -489,6 +494,11 @@ public class Player /// public static Event ChangingSpectatedPlayer { get; set; } = new(); + /// + /// Invoked when a changes rooms. + /// + public static Event RoomChanged { get; set; } = new(); + /// /// Invoked before a toggles the NoClip mode. /// @@ -684,6 +694,12 @@ public class Player /// The instance. public static void OnCancelledItemUse(CancelledItemUseEventArgs ev) => CancelledItemUse.InvokeSafely(ev); + /// + /// Called after a 's aspect ratio changes. + /// + /// The instance. + public static void OnChangedRatio(ChangedRatioEventArgs ev) => ChangedRatio.InvokeSafely(ev); + /// /// Called after a interacted with something. /// @@ -799,6 +815,12 @@ public class Player /// The instance. public static void OnRemovedHandcuffs(RemovedHandcuffsEventArgs ev) => RemovedHandcuffs.InvokeSafely(ev); + /// + /// Called when a changes rooms. + /// + /// The instance. + public static void OnRoomChanged(RoomChangedEventArgs ev) => RoomChanged.InvokeSafely(ev); + /// /// Called before a escapes. /// diff --git a/EXILED/Exiled.Events/Handlers/Scp049.cs b/EXILED/Exiled.Events/Handlers/Scp049.cs index 94bb965746..f4a2010794 100644 --- a/EXILED/Exiled.Events/Handlers/Scp049.cs +++ b/EXILED/Exiled.Events/Handlers/Scp049.cs @@ -32,6 +32,11 @@ public static class Scp049 /// public static Event ActivatingSense { get; set; } = new(); + /// + /// Invoked before SCP-049 finish the good sense of the doctor ability. + /// + public static Event FinishingSense { get; set; } = new(); + /// /// Invoked before SCP-049 uses the call ability. /// @@ -60,6 +65,12 @@ public static class Scp049 /// The instance. public static void OnActivatingSense(ActivatingSenseEventArgs ev) => ActivatingSense.InvokeSafely(ev); + /// + /// Called before SCP-049 finish the good sense of the doctor ability. + /// + /// The instance. + public static void OnFinishingSense(FinishingSenseEventArgs ev) => FinishingSense.InvokeSafely(ev); + /// /// Called before SCP-049 starts the call ability. /// @@ -72,4 +83,4 @@ public static class Scp049 /// The instance. public static void OnAttacking(AttackingEventArgs ev) => Attacking.InvokeSafely(ev); } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Handlers/Scp096.cs b/EXILED/Exiled.Events/Handlers/Scp096.cs index 76a6052fb8..7ddf935fb5 100644 --- a/EXILED/Exiled.Events/Handlers/Scp096.cs +++ b/EXILED/Exiled.Events/Handlers/Scp096.cs @@ -32,6 +32,11 @@ public static class Scp096 /// public static Event AddingTarget { get; set; } = new(); + /// + /// Invoked before removing a target from SCP-096. + /// + public static Event RemovingTarget { get; set; } = new(); + /// /// Invoked before SCP-096 begins prying open a gate. /// @@ -65,6 +70,12 @@ public static class Scp096 /// The instance. public static void OnAddingTarget(AddingTargetEventArgs ev) => AddingTarget.InvokeSafely(ev); + /// + /// Called before removing a target from SCP-096. + /// + /// The instance. + public static void OnRemovingTarget(RemovingTargetEventArgs ev) => RemovingTarget.InvokeSafely(ev); + /// /// Called before SCP-096 begins prying open a gate. /// diff --git a/EXILED/Exiled.Events/Handlers/Scp914.cs b/EXILED/Exiled.Events/Handlers/Scp914.cs index a9efe220a1..8d271661df 100644 --- a/EXILED/Exiled.Events/Handlers/Scp914.cs +++ b/EXILED/Exiled.Events/Handlers/Scp914.cs @@ -22,11 +22,21 @@ public static class Scp914 /// public static Event UpgradingPickup { get; set; } = new(); + /// + /// Invoked after SCP-914 upgrades a Pickup. + /// + public static Event UpgradedPickup { get; set; } = new(); + /// /// Invoked before SCP-914 upgrades an item in a player's inventory. /// public static Event UpgradingInventoryItem { get; set; } = new(); + /// + /// Invoked after SCP-914 upgrades an item in a player's inventory. + /// + public static Event UpgradedInventoryItem { get; set; } = new(); + /// /// Invoked before SCP-914 upgrades a player. /// @@ -48,12 +58,24 @@ public static class Scp914 /// The instance. public static void OnUpgradingPickup(UpgradingPickupEventArgs ev) => UpgradingPickup.InvokeSafely(ev); + /// + /// Called after SCP-914 upgrades a item. + /// + /// The instance. + public static void OnUpgradedPickup(UpgradedPickupEventArgs ev) => UpgradedPickup.InvokeSafely(ev); + /// /// Called before SCP-914 upgrades an item in a player's inventory. /// /// The instance. public static void OnUpgradingInventoryItem(UpgradingInventoryItemEventArgs ev) => UpgradingInventoryItem.InvokeSafely(ev); + /// + /// Called after SCP-914 upgrades an item in a player's inventory. + /// + /// The instance. + public static void OnUpgradedInventoryItem(UpgradedInventoryItemEventArgs ev) => UpgradedInventoryItem.InvokeSafely(ev); + /// /// Called before SCP-914 upgrades a player. /// diff --git a/EXILED/Exiled.Events/Handlers/Scp939.cs b/EXILED/Exiled.Events/Handlers/Scp939.cs index d2ca5e4533..d5d5da378e 100644 --- a/EXILED/Exiled.Events/Handlers/Scp939.cs +++ b/EXILED/Exiled.Events/Handlers/Scp939.cs @@ -74,6 +74,11 @@ public static class Scp939 /// public static Event ValidatingVisibility { get; set; } = new(); + /// + /// Invoked before mimicry point is placed. + /// + public static Event PlacingMimicPoint { get; set; } = new(); + /// /// Called before SCP-939 changes its target focus. /// @@ -139,5 +144,11 @@ public static class Scp939 /// /// The instance. public static void OnValidatingVisibility(ValidatingVisibilityEventArgs ev) => ValidatingVisibility.InvokeSafely(ev); + + /// + /// Called before mimicry point is placed. + /// + /// The instance. + public static void OnPlacingMimicPoint(PlacingMimicPointEventArgs ev) => PlacingMimicPoint.InvokeSafely(ev); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Server.cs b/EXILED/Exiled.Events/Handlers/Server.cs index 7ddffb003a..80453625bb 100644 --- a/EXILED/Exiled.Events/Handlers/Server.cs +++ b/EXILED/Exiled.Events/Handlers/Server.cs @@ -32,6 +32,11 @@ public static class Server /// public static Event RoundStarted { get; set; } = new(); + /// + /// Invoked after all players have spawned at the start of a new round. + /// + public static Event AllPlayersSpawned { get; set; } = new(); + /// /// Invoked before ending a round. /// @@ -137,6 +142,11 @@ public static class Server /// public static void OnRoundStarted() => RoundStarted.InvokeSafely(); + /// + /// Called after all players have spawned at the start of a new round. + /// + public static void OnAllPlayersSpawned() => AllPlayersSpawned.InvokeSafely(); + /// /// Called before ending a round. /// @@ -245,4 +255,4 @@ public static class Server /// The instance. public static void OnCompletingObjective(CompletingObjectiveEventArgs ev) => CompletingObjective.InvokeSafely(ev); } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Patches/Events/Item/Inspect.cs b/EXILED/Exiled.Events/Patches/Events/Item/Inspect.cs new file mode 100644 index 0000000000..860d064ea1 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Item/Inspect.cs @@ -0,0 +1,319 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Item +{ +#pragma warning disable SA1402 +#pragma warning disable SA1649 + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Item; + using HarmonyLib; + using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Jailbird; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.MicroHID.Modules; + using InventorySystem.Items.Usables.Scp1344; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add and event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectedItem))] + [HarmonyPatch(typeof(SimpleInspectorModule), nameof(SimpleInspectorModule.ServerProcessCmd))] + internal class InspectWeapon + { + private static IEnumerable Transpiler(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0); + + newInstructions.InsertRange(index, new[] + { + // this.Firearm + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Callvirt, PropertyGetter(typeof(SimpleInspectorModule), nameof(SimpleInspectorModule.Firearm))), + + // true + new(OpCodes.Ldc_I4_1), + + // InspectingItemEventArgs ev = new(this.Firearm) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectingItemEventArgs))[0]), + + // Handlers.Item.OnInspectingItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectingItem))), + }); + + newInstructions.InsertRange(newInstructions.Count - 1, new CodeInstruction[] + { + // this.Firearm + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(SimpleInspectorModule), nameof(SimpleInspectorModule.Firearm))), + + // InspectedItemEventArgs ev = new(this.Firearm) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Item.OnInspectedItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches + /// to add and event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectedItem))] + [HarmonyPatch(typeof(Scp1344NetworkHandler), nameof(Scp1344NetworkHandler.TryInspect))] + internal class InspectScp1344 + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = 1; + int index = newInstructions.FindIndex(x => x.Is(OpCodes.Callvirt, PropertyGetter(typeof(Scp1344Item), nameof(Scp1344Item.AllowInspect)))) + offset; + + LocalBuilder allowed = generator.DeclareLocal(typeof(bool)); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // store isAllowed + new(OpCodes.Stloc_S, allowed.LocalIndex), + + // curInstance + new(OpCodes.Ldloc_0), + + // load isAllowed + new(OpCodes.Ldloc_S, allowed.LocalIndex), + + // InspectingItemEventArgs ev = new(curInstance, isAllowed); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectingItemEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Item.OnInspectingItem(ev); + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectingItem))), + + // load isAllowed + new(OpCodes.Callvirt, PropertyGetter(typeof(InspectingItemEventArgs), nameof(InspectingItemEventArgs.IsAllowed))), + }); + + newInstructions.InsertRange(newInstructions.Count - 1, new CodeInstruction[] + { + // curInstance + new(OpCodes.Ldloc_0), + + // InspectedItemEventArgs = new(curInstance); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Item.OnInspectedItem(ev); + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches + /// to add and event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectedItem))] + [HarmonyPatch(typeof(KeycardItem), nameof(KeycardItem.ServerProcessCmd))] + internal class InspectKeycard + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = 1; + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldloc_1) + offset; + + LocalBuilder allowed = generator.DeclareLocal(typeof(bool)); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // save flag value as isAllowed + new(OpCodes.Stloc_S, allowed.LocalIndex), + + // this + new(OpCodes.Ldarg_0), + + // load isAllowed + new(OpCodes.Ldloc_S, allowed.LocalIndex), + + // InspectingItemEventArgs ev = new(this, isAllowed) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectingItemEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Item.OnInspectingItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectingItem))), + + // load isAllowed + new(OpCodes.Callvirt, PropertyGetter(typeof(InspectingItemEventArgs), nameof(InspectingItemEventArgs.IsAllowed))), + }); + + newInstructions.InsertRange(newInstructions.Count - 1, new CodeInstruction[] + { + // this + new(OpCodes.Ldarg_0), + + // InspectedItemEventArgs ev = new(this) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Item.OnInspectedItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + + private static void Return(KeycardItem item) => item.ServerSendPrivateRpc(x => KeycardItem.WriteInspect(x, false)); + } + + /// + /// Patches + /// to add and event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectedItem))] + [HarmonyPatch(typeof(JailbirdItem), nameof(JailbirdItem.ServerProcessCmd))] + internal class InspectJailbird + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = 1; + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldloc_2) + offset; + + LocalBuilder allowed = generator.DeclareLocal(typeof(bool)); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // save flag value as isAllowed + new(OpCodes.Stloc_S, allowed.LocalIndex), + + // this + new(OpCodes.Ldarg_0), + + // load isAllowed + new(OpCodes.Ldloc_S, allowed.LocalIndex), + + // InspectingItemEventArgs ev = new(this, isAllowed) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectingItemEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Item.OnInspectingItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectingItem))), + + // load isAllowed + new(OpCodes.Callvirt, PropertyGetter(typeof(InspectingItemEventArgs), nameof(InspectingItemEventArgs.IsAllowed))), + }); + + index = newInstructions.FindLastIndex(x => x.Calls(Method(typeof(JailbirdItem), nameof(JailbirdItem.SendRpc)))) + offset; + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // this + new(OpCodes.Ldarg_0), + + // InspectedItemEventArgs ev = new(this) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Item.OnInspectedItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches + /// to add and event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.InspectedItem))] + [HarmonyPatch(typeof(DrawAndInspectorModule), nameof(DrawAndInspectorModule.ServerProcessCmd))] + internal class InspectMicroHid + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0); + + Label returnLabel = generator.DefineLabel(); + + newInstructions.InsertRange(index, new[] + { + // this.MicroHid + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Callvirt, PropertyGetter(typeof(DrawAndInspectorModule), nameof(DrawAndInspectorModule.MicroHid))), + + // true + new(OpCodes.Ldc_I4_1), + + // InspectingItemEventArgs ev = new(this.MicroHid, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectingItemEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Item.OnInspectingItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectingItem))), + + // if (!ev.IsAllowed) + // return + new(OpCodes.Callvirt, PropertyGetter(typeof(InspectingItemEventArgs), nameof(InspectingItemEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, returnLabel), + }); + + newInstructions.InsertRange(newInstructions.Count - 1, new CodeInstruction[] + { + // this.MicroHid + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(DrawAndInspectorModule), nameof(DrawAndInspectorModule.MicroHid))), + + // InspectedItemEventArgs = new(this.MicroHid) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Item.OnInspectedItem(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + newInstructions[newInstructions.Count - 1].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/Item/JailbirdPatch.cs b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdPatch.cs index 8ffa6e06e4..653afada85 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdPatch.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdPatch.cs @@ -27,6 +27,7 @@ namespace Exiled.Events.Patches.Events.Item /// [EventPatch(typeof(Item), nameof(Item.Swinging))] [EventPatch(typeof(Item), nameof(Item.ChargingJailbird))] + [EventPatch(typeof(Item), nameof(Item.JailbirdChargeComplete))] [HarmonyPatch(typeof(JailbirdItem), nameof(JailbirdItem.ServerProcessCmd))] internal static class JailbirdPatch { @@ -83,7 +84,7 @@ private static bool HandleJailbird(JailbirdItem instance, JailbirdMessageType me return ev.IsAllowed; } - case JailbirdMessageType.ChargeStarted: + case JailbirdMessageType.ChargeLoadTriggered: { ChargingJailbirdEventArgs ev = new(instance.Owner, instance); @@ -91,6 +92,19 @@ private static bool HandleJailbird(JailbirdItem instance, JailbirdMessageType me if (ev.IsAllowed) return true; + ev.Player.RemoveHeldItem(destroy: false); + ev.Player.CurrentItem = ev.Item; + return false; + } + + case JailbirdMessageType.ChargeStarted: + { + JailbirdChargeCompleteEventArgs ev = new(instance.Owner, instance); + + Item.OnJailbirdChargeComplete(ev); + if (ev.IsAllowed) + return true; + ev.Player.RemoveHeldItem(destroy: false); ev.Player.AddItem(ev.Item); return false; diff --git a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs index df768a3048..bac813d138 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs @@ -11,18 +11,22 @@ namespace Exiled.Events.Patches.Events.Map using System.Reflection.Emit; using API.Features.Doors; - using API.Features.Pickups; using API.Features.Pools; - using Exiled.Events.Attributes; - using Exiled.Events.EventArgs.Map; - using Handlers; + using Attributes; + + using EventArgs.Map; + using HarmonyLib; + using Interactables.Interobjects.DoorUtils; + using MapGeneration.Distributors; using static HarmonyLib.AccessTools; + using Map = Handlers.Map; + /// /// Patches . /// Adds the event. @@ -40,6 +44,9 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Ldfld) + offset; + index = newInstructions.FindLastIndex(i => i.LoadsField(Field(typeof(DoorVariantExtension), nameof(DoorVariantExtension.TargetDoor)))) + offset; + + newInstructions[index].WithLabels(allowOriginalLogic); - newInstructions.RemoveRange(index, 2); + int temp_instr = newInstructions.FindLastIndex(i => i.Calls(PropertyGetter(typeof(UnityEngine.Component), nameof(UnityEngine.Component.gameObject)))) - 1; + newInstructions[temp_instr].WithLabels(loadGameObjectLocation); newInstructions.InsertRange(index, new[] { // ev.Door.Base - new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex), + new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Brfalse, allowOriginalLogic), + new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SpawningItemEventArgs), nameof(SpawningItemEventArgs.TriggerDoor))), + new(OpCodes.Brfalse, allowOriginalLogic), + new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), new(OpCodes.Callvirt, PropertyGetter(typeof(SpawningItemEventArgs), nameof(SpawningItemEventArgs.TriggerDoor))), new(OpCodes.Callvirt, PropertyGetter(typeof(Door), nameof(Door.Base))), + new(OpCodes.Br, loadGameObjectLocation), }); - newInstructions[newInstructions.Count - 1].WithLabels(returnLabel); - for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs new file mode 100644 index 0000000000..8a12f60f94 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Player +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CentralAuth; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [HarmonyPatch(typeof(AspectRatioSync), nameof(AspectRatioSync.UserCode_CmdSetAspectRatio__Single))] + internal class ChangedAspectRatio + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder oldratio = generator.DeclareLocal(typeof(float)); + LocalBuilder hub = generator.DeclareLocal(typeof(ReferenceHub)); + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // float OldRatio = this.AspectRatio; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + new(OpCodes.Stloc_S, oldratio.LocalIndex), + }); + + int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ret); + newInstructions[index].WithLabels(retLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // ReferenceHub hub = this.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, hub.LocalIndex), + + // if (hub.authManager._targetInstanceMode != ClientInstanceMode.ReadyClient) return; + new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.authManager))), + new(OpCodes.Ldfld, Field(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager._targetInstanceMode))), + new(OpCodes.Ldc_I4, 1), + new(OpCodes.Bne_Un_S, retLabel), + + // hub + new(OpCodes.Ldloc, hub.LocalIndex), + + // OldRatio + new(OpCodes.Ldloc, oldratio.LocalIndex), + + // this.AspectRatio + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + + // ChangedRatioEventArgs ev = new ChangedRatioEventArgs(ReferenceHub, float, float) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ChangedRatioEventArgs))[0]), + + // Handlers.Player.OnChangedRatio(ev); + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnChangedRatio))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs new file mode 100644 index 0000000000..e53138f38b --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs @@ -0,0 +1,91 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Player +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + using Exiled.Events.Handlers; + using HarmonyLib; + using MapGeneration; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to add the event. + /// + [EventPatch(typeof(Player), nameof(Player.RoomChanged))] + [HarmonyPatch(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache.ValidateCache))] + internal class ChangedRoom + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label returnLabel = generator.DefineLabel(); + + LocalBuilder oldRoom = generator.DeclareLocal(typeof(RoomIdentifier)); + LocalBuilder newRoom = generator.DeclareLocal(typeof(RoomIdentifier)); + + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldloca_S); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // RoomIdentifier oldRoom = this._lastDetected + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._lastDetected))), + new(OpCodes.Stloc_S, oldRoom), + }); + + int lastIndex = newInstructions.Count - 1; + + newInstructions[lastIndex].WithLabels(returnLabel); + + newInstructions.InsertRange(lastIndex, new CodeInstruction[] + { + // newRoom = lastDetected + new(OpCodes.Ldloc_1), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, newRoom), + + // oldRoom + new(OpCodes.Ldloc_S, oldRoom), + + // if (oldRoom == newRoom) return; + new(OpCodes.Call, Method(typeof(object), nameof(object.ReferenceEquals), new[] { typeof(object), typeof(object) })), + new(OpCodes.Brtrue_S, returnLabel), + + // this._roleManager.gameObject.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._roleManager))), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + + // oldRoom + new(OpCodes.Ldloc_S, oldRoom), + + // newRoom + new(OpCodes.Ldloc_S, newRoom), + + // RoomChangedEventArgs ev = new RoomChangedEventArgs(hub, oldRoom, newRoom); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RoomChangedEventArgs))[0]), + + // Handlers.Player.OnRoomChanged(ev); + new(OpCodes.Call, Method(typeof(Player), nameof(Player.OnRoomChanged))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Player/DroppingItem.cs b/EXILED/Exiled.Events/Patches/Events/Player/DroppingItem.cs index d10d6cb2dd..f5802d2a84 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/DroppingItem.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/DroppingItem.cs @@ -107,7 +107,7 @@ private static IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Stloc_1) + offset; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Stloc_2) + offset; newInstructions.InsertRange(index, new CodeInstruction[] { @@ -116,7 +116,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Pop); + + newInstructions[index] = new CodeInstruction(OpCodes.Stloc, ragdoll.LocalIndex); + newInstructions.InsertRange( newInstructions.Count - 1, new CodeInstruction[] @@ -90,6 +96,9 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Patches/Events/Player/EscapingAndEscaped.cs b/EXILED/Exiled.Events/Patches/Events/Player/EscapingAndEscaped.cs index 72bade7906..5b778fb0c9 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/EscapingAndEscaped.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/EscapingAndEscaped.cs @@ -13,14 +13,13 @@ namespace Exiled.Events.Patches.Events.Player using System.Collections.Generic; using System.Reflection.Emit; - using API.Enums; using API.Features; using API.Features.Pools; using EventArgs.Player; using Exiled.API.Features.Roles; using Exiled.Events.Attributes; using HarmonyLib; - using PlayerRoles.FirstPersonControl; + using LabApi.Events.Arguments.PlayerEvents; using static HarmonyLib.AccessTools; @@ -41,8 +40,8 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Newobj) + offset; + int offset = 2; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Callvirt && i.operand == (object)PropertyGetter(typeof(PlayerEscapingEventArgs), nameof(PlayerEscapingEventArgs.EscapeScenario))) + offset; newInstructions.InsertRange( index, diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Interacted.cs b/EXILED/Exiled.Events/Patches/Events/Player/Interacted.cs index 1b5a21c170..3c6c6e7f30 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Interacted.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Interacted.cs @@ -14,19 +14,18 @@ namespace Exiled.Events.Patches.Events.Player using API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Player; - using HarmonyLib; - + using Interactables; using UnityEngine; using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.Interacted))] - [HarmonyPatch(typeof(PlayerInteract), nameof(PlayerInteract.OnInteract))] + [HarmonyPatch(typeof(InteractionCoordinator), nameof(InteractionCoordinator.ClientInteract))] internal static class Interacted { private static IEnumerable Transpiler(IEnumerable instructions) @@ -39,7 +38,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp049 +{ +#pragma warning disable SA1402 // File may only contain a single type + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp049; + using HarmonyLib; + + using PlayerRoles.PlayableScps.Scp049; + using PlayerRoles.Subroutines; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Scp049), nameof(Handlers.Scp049.FinishingSense))] + [HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.ServerLoseTarget))] + internal class FinishingSense + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(FinishingSenseEventArgs)); + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // this.Owner + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Owner))), + + // this.Target + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Target))), + + // Scp049SenseAbility.TargetLostCooldown + new(OpCodes.Ldc_R8, (double)Scp049SenseAbility.TargetLostCooldown), + + // true (IsAllowed) + new(OpCodes.Ldc_I4_1), + + // FinishingSenseEventArgs ev = new FinishingSenseEventArgs(ReferenceHub, ReferenceHub, double, bool) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(FinishingSenseEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp049.OnFinishingSense(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp049), nameof(Handlers.Scp049.OnFinishingSense))), + + // if (!ev.IsAllowed) return; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + }); + + // this.Cooldown.Trigger((double)Scp049SenseAbility.TargetLostCooldown) index + int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldc_R8 && (double)i.operand == Scp049SenseAbility.TargetLostCooldown); + + // Replace "this.Cooldown.Trigger((double)Scp049SenseAbility.ReducedCooldown)" with "this.Cooldown.Trigger((double)ev.cooldowntime)" + newInstructions.RemoveAt(index); + newInstructions.InsertRange(index, new CodeInstruction[] + { + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.CooldownTime))), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Scp049), nameof(Handlers.Scp049.FinishingSense))] + [HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.ServerProcessKilledPlayer))] + internal class FinishingSense2 + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(FinishingSenseEventArgs)); + + // Continue label for isAllowed check + Label retLabel = generator.DefineLabel(); + + // this.Cooldown.Trigger(Scp049SenseAbility.BaseCooldown) index + int offset = -2; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldc_R8 && (double)i.operand == Scp049SenseAbility.BaseCooldown) + offset; + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // this.Owner + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Owner))), + + // this.Target + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Target))), + + // double CooldownTime = Scp049SenseAbility.BaseCooldown; + new(OpCodes.Ldc_R8, (double)Scp049SenseAbility.BaseCooldown), + + // true (IsAllowed) + new(OpCodes.Ldc_I4_1), + + // FinishingSenseEventArgs ev = new FinishingSenseEventArgs(ReferenceHub, ReferenceHub, double, bool) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(FinishingSenseEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp049.OnFinishingSense(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp049), nameof(Handlers.Scp049.OnFinishingSense))), + + // if (!ev.IsAllowed) return; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + }); + + // this.Cooldown.Trigger(Scp049SenseAbility.BaseCooldown) index + index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldc_R8 && (double)i.operand == Scp049SenseAbility.BaseCooldown); + + newInstructions.RemoveAt(index); + newInstructions.InsertRange(index, new CodeInstruction[] + { + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.CooldownTime))), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Scp049), nameof(Handlers.Scp049.FinishingSense))] + [HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.ServerProcessCmd), new[] { typeof(Mirror.NetworkReader) })] + internal class FinishingSense3 + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(FinishingSenseEventArgs)); + LocalBuilder isAbilityActive = generator.DeclareLocal(typeof(bool)); + + Label skipactivatingsense = generator.DefineLabel(); + Label continueLabel = generator.DefineLabel(); + Label allowed = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // isAbilityActive = this.HasTarget; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.HasTarget))), + new(OpCodes.Stloc, isAbilityActive.LocalIndex), + }); + + // this.Cooldown.Trigger(2.5) index + int offset = -1; + int index = newInstructions.FindIndex(i => + i.opcode == OpCodes.Ldfld && + i.operand == (object)Field(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Cooldown))) + offset; + + newInstructions[index].labels.Add(continueLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // Skip if the ability is not active and this is an unsuccessful attempt + new(OpCodes.Ldloc, isAbilityActive.LocalIndex), + new(OpCodes.Brfalse_S, continueLabel), + + // this.Owner; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Owner))), + + // this.Target; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.Target))), + + // Scp049SenseAbility.AttemptFailCooldown; + new(OpCodes.Ldc_R8, (double)Scp049SenseAbility.AttemptFailCooldown), + + // true (IsAllowed) + new(OpCodes.Ldc_I4_1), + + // FinishingSenseEventArgs ev = new FinishingSenseEventArgs(ReferenceHub, ReferenceHub, double, bool) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(FinishingSenseEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp049.OnFinishingSense(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp049), nameof(Handlers.Scp049.OnFinishingSense))), + + // if (!ev.IsAllowed) return; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, allowed), + + // If not allowed, set has target to true so as not to break the sense ability + // this.HasTarget = true; + new(OpCodes.Ldarg_0), + new(OpCodes.Ldc_I4_1), + new(OpCodes.Callvirt, PropertySetter(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.HasTarget))), + + // return; + new(OpCodes.Ret), + + // this.Cooldown.Trigger(ev.cooldown.time) + new CodeInstruction(OpCodes.Ldarg_0).WithLabels(allowed), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingSenseEventArgs), nameof(FinishingSenseEventArgs.CooldownTime))), + new(OpCodes.Callvirt, Method(typeof(AbilityCooldown), nameof(AbilityCooldown.Trigger), new[] { typeof(double) })), + + new(OpCodes.Br_S, skipactivatingsense), + }); + + offset = -2; + index = newInstructions.FindIndex(i => + i.opcode == OpCodes.Call && + i.operand == (object)Method(typeof(SubroutineBase), nameof(SubroutineBase.ServerSendRpc), new[] { typeof(bool) })) + offset; + + newInstructions[index].labels.Add(skipactivatingsense); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs b/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs new file mode 100644 index 0000000000..869988bda6 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs @@ -0,0 +1,149 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp096 +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Reflection.Emit; + + using API.Features; + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp096; + using HarmonyLib; + using PlayerRoles.PlayableScps.Scp096; + using PlayerRoles.Subroutines; + + using static HarmonyLib.AccessTools; + + /// + /// Patches and . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Scp096), nameof(Handlers.Scp096.RemovingTarget))] + [HarmonyPatch(typeof(Scp096TargetsTracker))] + internal static class RemovingTarget + { + [HarmonyPatch(nameof(Scp096TargetsTracker.RemoveTarget))] + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retFalseLabel = generator.DefineLabel(); + Label runLabel = generator.DefineLabel(); + + // make game check contains instead of removing then forcibly running our event (so no spam event calls and is still deniable) + newInstructions.Find(instruction => instruction.Calls(Method(typeof(HashSet), nameof(HashSet.Remove)))).operand = Method(typeof(HashSet), nameof(HashSet.Contains)); + + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) - 1; + + newInstructions[index].WithLabels(retFalseLabel); + + index -= 1; + + // integrate our condition into first if statement + newInstructions.RemoveAt(index); + + newInstructions.InsertRange( + index, + new CodeInstruction[] + { + // integrate our condition into first if statement + new(OpCodes.Brfalse, retFalseLabel), + + // Player.Get(base.Owner) + 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) })), + + // Player.Get(target) + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // true + new(OpCodes.Ldc_I4_1), + + // RemovingTargetEventArgs ev = new(scp096, target, isAllowed) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Scp096.OnRemovingTarget(ev) + new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))), + new(OpCodes.Brtrue, runLabel), + }); + + index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + 1; + + // if allowed, remove target + newInstructions.InsertRange(index, new[] + { + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]).WithLabels(runLabel), + new(OpCodes.Ldfld, Field(typeof(Scp096TargetsTracker), nameof(Scp096TargetsTracker.Targets))), + new(OpCodes.Ldarg_1), + new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Remove))), + new(OpCodes.Pop), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + + [HarmonyPatch(nameof(Scp096TargetsTracker.ClearAllTargets))] + [HarmonyTranspiler] + private static IEnumerable Transpiler2(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + object continueLabel = newInstructions.Find(instruction => instruction.opcode == OpCodes.Br_S).operand; + + const int offset = 1; + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stloc_1) + offset; + + newInstructions.InsertRange( + index, + new[] + { + // Player.Get(base.Owner) + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(target) + new(OpCodes.Ldloc_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // true + new(OpCodes.Ldc_I4_1), + + // RemovingTargetEventArgs ev = new(scp096, target, isAllowed) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Scp096.OnRemovingTarget(ev) + new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))), + + // if (!ev.IsAllowed) + // continue; + new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))), + new(OpCodes.Brfalse, 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/Events/Scp173/BeingObserved.cs b/EXILED/Exiled.Events/Patches/Events/Scp173/BeingObserved.cs index 8bb058bf7c..a2c276d9d8 100755 --- a/EXILED/Exiled.Events/Patches/Events/Scp173/BeingObserved.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp173/BeingObserved.cs @@ -7,15 +7,20 @@ namespace Exiled.Events.Patches.Events.Scp173 { + using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; + using API.Features; + using Exiled.API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Scp173; using HarmonyLib; + + using PlayerRoles.PlayableScps; using PlayerRoles.PlayableScps.Scp173; using PlayerRoles.Subroutines; @@ -35,13 +40,12 @@ private static IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Brtrue_S) + offset; - + int offset = 4; + int index = newInstructions.FindIndex(x => x.Calls(PropertyGetter(typeof(VisionInformation), nameof(VisionInformation.IsLooking)))) + offset; newInstructions.InsertRange(index, new CodeInstruction[] { // Player.Get(target) - new(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldarg_1).MoveLabelsFrom(newInstructions[index]), new(OpCodes.Call, Method(typeof(API.Features.Player), nameof(API.Features.Player.Get), new[] { typeof(ReferenceHub) })), // Player.Get(base.Owner) @@ -78,4 +82,4 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Scp914/InteractingEvents.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/InteractingEvents.cs index 592b41623b..c042b65f9e 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp914/InteractingEvents.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/InteractingEvents.cs @@ -27,6 +27,7 @@ namespace Exiled.Events.Patches.Events.Scp914 /// Patches . /// Adds the event. /// + [EventPatch(typeof(Scp914), nameof(Scp914.ChangingKnobSetting))] [EventPatch(typeof(Scp914), nameof(Scp914.Activating))] [HarmonyPatch(typeof(Scp914Controller), nameof(Scp914Controller.ServerInteract))] internal static class InteractingEvents diff --git a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs new file mode 100644 index 0000000000..64842911da --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp914 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp914; + + using global::Scp914; + + using Handlers; + + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Scp914), nameof(Scp914.UpgradedPickup))] + [HarmonyPatch(typeof(Scp914Upgrader), nameof(Scp914Upgrader.ProcessPickup))] + internal static class UpgradedPickup + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldloc_2); + + List [EventPatch(typeof(Scp914), nameof(Scp914.UpgradingPickup))] [HarmonyPatch(typeof(Scp914Upgrader), nameof(Scp914Upgrader.ProcessPickup))] - internal static class UpgradingItem + internal static class UpgradingPickup { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { diff --git a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs index 394fd8815b..3927ed9a55 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Scp914 { using System.Collections.Generic; + using System.Reflection; using System.Reflection.Emit; using API.Features; @@ -22,6 +23,7 @@ namespace Exiled.Events.Patches.Events.Scp914 using static HarmonyLib.AccessTools; + using OpCode = System.Reflection.Emit.OpCode; using Scp914 = Handlers.Scp914; /// @@ -112,8 +114,20 @@ private static IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Stloc_S && x.operand is LocalBuilder { LocalIndex: 10 }) + offset; + // index = newInstructions.FindIndex(x => x.opcode == OpCodes.Stloc_S && x.operand is LocalBuilder { LocalIndex: 10 }) + offset; + ConstructorInfo lab_api_constructor = typeof(LabApi.Events.Arguments.Scp914Events.Scp914ProcessingInventoryItemEventArgs) + .GetConstructor(new[] + { + typeof(InventorySystem.Items.ItemBase), + typeof(Scp914KnobSetting), + typeof(ReferenceHub), + }); + index = newInstructions.FindIndex(x => x.Is(OpCodes.Newobj, lab_api_constructor)) + offset; + + // ridtp lcz914 + // noclip + // give tuxwonder7 47 newInstructions.InsertRange( index, new CodeInstruction[] @@ -127,7 +141,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Scp939/PlacingMimicPoint.cs b/EXILED/Exiled.Events/Patches/Events/Scp939/PlacingMimicPoint.cs new file mode 100644 index 0000000000..753291df01 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp939/PlacingMimicPoint.cs @@ -0,0 +1,84 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod 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; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp939; + using HarmonyLib; + using PlayerRoles.PlayableScps.Scp939.Mimicry; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add event. + /// + [EventPatch(typeof(Handlers.Scp939), nameof(Handlers.Scp939.PlacingMimicPoint))] + [HarmonyPatch(typeof(MimicPointController), nameof(MimicPointController.ServerProcessCmd))] + internal class PlacingMimicPoint + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = 1; + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Stfld) + offset; + + Label returnLabel = generator.DefineLabel(); + + LocalBuilder ev = generator.DeclareLocal(typeof(PlacingMimicPointEventArgs)); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // Player.Get(this.Owner) + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(MimicPointController), nameof(MimicPointController.Owner))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // this._syncPos + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(MimicPointController), nameof(MimicPointController._syncPos))), + + // true + new(OpCodes.Ldc_I4_1), + + // PlacingMimicPointEventArgs = new(Player.Get(this.Owner), this._syncPos, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(PlacingMimicPointEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp939.OnPlacingMimicPoint(ev) + new(OpCodes.Call, Method(typeof(Handlers.Scp939), nameof(Handlers.Scp939.OnPlacingMimicPoint))), + + // if (!ev.IsAllowed) + // return + new(OpCodes.Callvirt, PropertyGetter(typeof(PlacingMimicPointEventArgs), nameof(PlacingMimicPointEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, returnLabel), + + // this._syncPos = ev.Position + new(OpCodes.Ldarg_0), + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(PlacingMimicPointEventArgs), nameof(PlacingMimicPointEventArgs.Position))), + new(OpCodes.Stfld, Field(typeof(MimicPointController), nameof(MimicPointController._syncPos))), + }); + + newInstructions[newInstructions.Count - 1].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/Server/RoundEnd.cs b/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs index 65a9abf52a..fba037de79 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs @@ -66,8 +66,8 @@ private static IEnumerable Transpiler(IEnumerable), nameof(HashSet.Contains))), + new(OpCodes.Ldloc_S, 20), + new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Contains))), new(OpCodes.Brtrue_S, jmp), }); diff --git a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs index 1ca476b7b9..498c487f0c 100644 --- a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs +++ b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Warhead { using System.Collections.Generic; + using System.Reflection; using System.Reflection.Emit; using API.Features; @@ -22,11 +23,12 @@ namespace Exiled.Events.Patches.Events.Warhead using Warhead = Handlers.Warhead; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Warhead), nameof(Warhead.ChangingLeverStatus))] - [HarmonyPatch(typeof(PlayerInteract), nameof(PlayerInteract.UserCode_CmdUsePanel__AlphaPanelOperations))] + + [HarmonyPatch(typeof(AlphaWarheadNukesitePanel), nameof(AlphaWarheadNukesitePanel.ServerInteract))] internal static class ChangingLeverStatus { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) @@ -35,19 +37,18 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Brtrue_S) + offset; + int index = newInstructions.FindIndex(x => x.operand == (object)PropertySetter(typeof(AlphaWarheadNukesitePanel), nameof(AlphaWarheadNukesitePanel.Networkenabled))) - 5; newInstructions.InsertRange( index, new[] { // Player.Get(component) - new CodeInstruction(OpCodes.Ldloc_0).MoveLabelsFrom(newInstructions[index]), + new CodeInstruction(OpCodes.Ldarg_1).MoveLabelsFrom(newInstructions[index]), new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), // nukeside.Networkenabled - new(OpCodes.Ldloc_1), + new(OpCodes.Ldarg_0), new(OpCodes.Call, PropertyGetter(typeof(AlphaWarheadNukesitePanel), nameof(AlphaWarheadNukesitePanel.Networkenabled))), // true diff --git a/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs b/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs new file mode 100644 index 0000000000..544ef5086e --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ +#pragma warning disable SA1313 +#pragma warning disable SA1402 + + using HarmonyLib; + using InventorySystem.Items.Firearms.Attachments; + + /// + /// Patch for adding to list. + /// + [HarmonyPatch(typeof(WorkstationController), nameof(WorkstationController.Start))] + internal class WorkstationListAdd + { + private static void Postfix(WorkstationController __instance) + { + API.Features.Workstation.Get(__instance); + } + } + + /// + /// Patch for removing to list. + /// + [HarmonyPatch(typeof(WorkstationController), nameof(WorkstationController.OnDestroy))] + internal class WorkstationListRemove + { + private static void Postfix(WorkstationController __instance) + { + API.Features.Workstation.WorkstationControllerToWorkstation.Remove(__instance); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Installer/Program.cs b/EXILED/Exiled.Installer/Program.cs index 8b9c39c7e8..c7a43a2dcd 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 = 984817990; + private const long RepoID = 833723500; private const string ExiledAssetName = "exiled.tar.gz"; // This is the lowest version the installer will check to install diff --git a/EXILED/Exiled.Installer/README.md b/EXILED/Exiled.Installer/README.md index c2033165c2..ad658da899 100644 --- a/EXILED/Exiled.Installer/README.md +++ b/EXILED/Exiled.Installer/README.md @@ -43,7 +43,7 @@ Trying to find release.. Release found! PRE: True | ID: 87710626 | TAG: 6.0.0-beta.18 Asset found! -ID: 90263995 | NAME: Exiled.tar.gz | SIZE: 1027928 | URL: https://api.github.com/repos/ExSLMod-Team/Exiled-EA/releases/assets/90263995 | DownloadURL: https://github.com/ExSLMod-Team/Exiled-EA/releases/download/6.0.0-beta.18/Exiled.tar.gz +ID: 90263995 | NAME: Exiled.tar.gz | SIZE: 1027928 | URL: https://api.github.com/repos/Exmod-Team/Exiled-EA/releases/assets/90263995 | DownloadURL: https://github.com/Exmod-Team/Exiled-EA/releases/download/6.0.0-beta.18/Exiled.tar.gz Processing 'EXILED/Plugins/dependencies/0Harmony.dll' Extracting '0Harmony.dll' into 'YourAppDataPath/EXILED/Plugins/dependencies/0Harmony.dll'... Processing 'EXILED/Plugins/dependencies/Exiled.API.dll' @@ -88,7 +88,7 @@ Trying to find release.. Release found! PRE: False | ID: 87710626 | TAG: 6.0.0-beta.18 Asset found! -ID: 90263995 | NAME: Exiled.tar.gz | SIZE: 1027928 | URL: https://api.github.com/repos/ExSLMod-Team/Exiled-EA/releases/assets/90263995 | DownloadURL: https://github.com/ExSLMod-Team/Exiled-EA/releases/download/6.0.0-beta.18/Exiled.tar.gz +ID: 90263995 | NAME: Exiled.tar.gz | SIZE: 1027928 | URL: https://api.github.com/repos/Exmod-Team/Exiled-EA/releases/assets/90263995 | DownloadURL: https://github.com/Exmod-Team/Exiled-EA/releases/download/6.0.0-beta.18/Exiled.tar.gz Processing 'EXILED/Plugins/dependencies/0Harmony.dll' Extracting '0Harmony.dll' into '/user/SCP/EXILED/Plugins/dependencies/0Harmony.dll'... Processing 'EXILED/Plugins/dependencies/Exiled.API.dll' diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 8490d01eea..4c9d833bc4 100644 --- a/EXILED/Exiled.Loader/AutoUpdateFiles.cs +++ b/EXILED/Exiled.Loader/AutoUpdateFiles.cs @@ -17,6 +17,6 @@ public static class AutoUpdateFiles /// /// Gets which SCP: SL version generated Exiled. /// - public static readonly Version RequiredSCPSLVersion = new(14, 1, 0, 0); + public static readonly Version RequiredSCPSLVersion = new(14, 1, 0, 1); } } \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs index 7c2194d09f..18ef26fdcd 100644 --- a/EXILED/Exiled.Loader/Loader.cs +++ b/EXILED/Exiled.Loader/Loader.cs @@ -26,7 +26,6 @@ namespace Exiled.Loader using Features; using Features.Configs; using Features.Configs.CustomConverters; - using MEC; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NodeDeserializers; @@ -117,39 +116,15 @@ public Loader() .Build(); /// - /// Loads all plugins. + /// Loads all plugins, both globals and locals. /// public static void LoadPlugins() { File.Delete(Path.Combine(Paths.Plugins, "Exiled.Updater.dll")); File.Delete(Path.Combine(Paths.Dependencies, "Exiled.API.dll")); - foreach (string assemblyPath in Directory.GetFiles(Paths.Plugins, "*.dll")) - { - Assembly assembly = LoadAssembly(assemblyPath); - - if (assembly is null) - continue; - - Locations[assembly] = assemblyPath; - } - - foreach (Assembly assembly in Locations.Keys) - { - if (Locations[assembly].Contains("dependencies")) - continue; - - IPlugin plugin = CreatePlugin(assembly); - - if (plugin is null) - continue; - - AssemblyInformationalVersionAttribute attribute = plugin.Assembly.GetCustomAttribute(); - - Log.Info($"Loaded plugin {plugin.Name}@{(attribute is not null ? attribute.InformationalVersion : plugin.Version is not null ? $"{plugin.Version.Major}.{plugin.Version.Minor}.{plugin.Version.Build}" : string.Empty)}"); - Server.PluginAssemblies.Add(assembly, plugin); - Plugins.Add(plugin); - } + LoadPluginsFromDirectory(); + LoadPluginsFromDirectory(Server.Port.ToString()); } /// @@ -487,6 +462,51 @@ private static bool CheckPluginRequiredExiledVersion(IPlugin plugin) return false; } + /// + /// Load every plugin inside the given directory, if null it's default EXILED one (global). + /// + /// The sub-directory of the plugin - if null the default EXILED one will be used. + private static void LoadPluginsFromDirectory(string dir = null) + { + string path = Paths.Plugins; + if (dir != null) + path = Path.Combine(path, dir); + + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + foreach (string assemblyPath in Directory.GetFiles(path, "*.dll")) + { + Assembly assembly = LoadAssembly(assemblyPath); + + if (assembly == null) + continue; + + Locations[assembly] = assemblyPath; + } + + foreach (Assembly assembly in Locations.Keys) + { + if (Locations[assembly].Contains("dependencies")) + continue; + + IPlugin plugin = CreatePlugin(assembly); + + if (plugin == null) + continue; + + if (Plugins.Any(p => p.Name == plugin.Name)) + continue; + + AssemblyInformationalVersionAttribute attribute = plugin.Assembly.GetCustomAttribute(); + + Log.Info($"Loaded plugin {plugin.Name}@{(attribute is not null ? attribute.InformationalVersion : plugin.Version is not null ? $"{plugin.Version.Major}.{plugin.Version.Minor}.{plugin.Version.Build}" : string.Empty)}"); + + Server.PluginAssemblies.Add(assembly, plugin); + Plugins.Add(plugin); + } + } + /// /// Attempts to load Embedded (compressed) assemblies from specified Assembly. /// @@ -662,4 +682,4 @@ private static void LoadDependencies() } } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 92c47415ee..0c70548bb4 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -32,7 +32,7 @@ namespace Exiled.Loader /// internal sealed class Updater { - private const long REPOID = 984817990; + 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"; @@ -136,7 +136,7 @@ private HttpClient CreateHttpClient() Timeout = TimeSpan.FromSeconds(480), }; - client.DefaultRequestHeaders.Add("User-Agent", $"Exiled.Loader (https://github.com/ExSLMod-Team/EXILED, {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)})"); + client.DefaultRequestHeaders.Add("User-Agent", $"Exiled.Loader (https://github.com/Exmod-Team/EXILED, {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)})"); return client; } diff --git a/EXILED/Exiled/Exiled.nuspec b/EXILED/Exiled/Exiled.nuspec index cf95b0fbc8..431aa120ee 100644 --- a/EXILED/Exiled/Exiled.nuspec +++ b/EXILED/Exiled/Exiled.nuspec @@ -9,9 +9,9 @@ ExMod-Team Copyright © ExMod Team 2024 - $year$ false - - https://github.com/ExSLMod-Team/EXILED/blob/master/LICENSE - https://github.com/ExSLMod-Team/EXILED + + https://github.com/Exmod-Team/EXILED/blob/master/LICENSE + https://github.com/Exmod-Team/EXILED images\Exiled_Icon.png Plugin framework for SCP: Secret Laboratory. diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 31ee11803d..f4d027231f 100644 --- a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md +++ b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md @@ -17,7 +17,7 @@ title: NW Documentation --- -Last Update (14.1.0.0) +Last Update (14.1.0.1) ### Index @@ -28,7 +28,6 @@ Last Update (14.1.0.0) - [ActionName](#actionname) - [Activity](#activity) - [AdminFlags](#adminflags) -- [AlphaPanelOperations](#alphapaneloperations) - [AnimItemLayer3p](#animitemlayer3p) - [AnimState3p](#animstate3p) - [AttachmentDescriptiveAdvantages](#attachmentdescriptiveadvantages) @@ -119,6 +118,7 @@ Last Update (14.1.0.0) - [ExampleId](#exampleid) - [ExampleId](#exampleid) - [ExplosionType](#explosiontype) +- [ExportPreset](#exportpreset) - [FacilityZone](#facilityzone) - [Faction](#faction) - [FailReason](#failreason) @@ -141,7 +141,6 @@ Last Update (14.1.0.0) - [FreezingMode](#freezingmode) - [FriendlyFireAction](#friendlyfireaction) - [FriendlyFireInteraction](#friendlyfireinteraction) -- [Generator079Operations](#generator079operations) - [GeneratorColliderId](#generatorcolliderid) - [GeneratorFlags](#generatorflags) - [GeoblockingMode](#geoblockingmode) @@ -211,13 +210,16 @@ Last Update (14.1.0.0) - [NtpMode](#ntpmode) - [NullableBoolValue](#nullableboolvalue) - [OpenerEventType](#openereventtype) +- [OptOutExportBehavior](#optoutexportbehavior) - [OpusApplicationType](#opusapplicationtype) - [OpusCtlGetRequest](#opusctlgetrequest) - [OpusCtlSetRequest](#opusctlsetrequest) - [OpusStatusCode](#opusstatuscode) +- [OtherAudioSetting](#otheraudiosetting) - [OtherCondition](#othercondition) - [OutputCodes](#outputcodes) - [PacketProperty](#packetproperty) +- [PanelColliderId](#panelcolliderid) - [ParameterMixingMode](#parametermixingmode) - [ParseResult](#parseresult) - [PDTeleportType](#pdteleporttype) @@ -326,7 +328,6 @@ Last Update (14.1.0.0) - [ValidationError](#validationerror) - [ValidationError](#validationerror) - [VariantType](#varianttype) -- [VcAudioSetting](#vcaudiosetting) - [VcMuteFlags](#vcmuteflags) - [VcPrivacyFlags](#vcprivacyflags) - [VersionType](#versiontype) @@ -486,17 +487,6 @@ Last Update (14.1.0.0) -### AlphaPanelOperations - -
PlayerInteract+AlphaPanelOperations - -``` - [0] = Cancel - [1] = Lever -``` - -
- ### AnimItemLayer3p
InventorySystem.Items.Thirdperson.AnimItemLayer3p @@ -1894,6 +1884,16 @@ Last Update (14.1.0.0)
+### ExportPreset + +
Metrics.DeathsCollector+ExportPreset + +``` + [0] = DeathReasons +``` + +
+ ### FacilityZone
MapGeneration.FacilityZone @@ -2197,18 +2197,6 @@ Last Update (14.1.0.0)
-### Generator079Operations - -
PlayerInteract+Generator079Operations - -``` - [0] = Door - [1] = Tablet - [2] = Cancel -``` - -
- ### GeneratorColliderId
MapGeneration.Distributors.Scp079Generator+GeneratorColliderId @@ -2346,6 +2334,7 @@ Last Update (14.1.0.0) [16] = PackedULong [17] = Scp330Hint [18] = SSKeybind + [19] = AnimationCurve ```
@@ -3331,6 +3320,18 @@ Last Update (14.1.0.0) +### OptOutExportBehavior + +
Metrics.ScpPreferencesCollector+OptOutExportBehavior + +``` + [0] = Include + [1] = Exclude + [2] = TreatAsZero +``` + +
+ ### OpusApplicationType
VoiceChat.Codec.Enums.OpusApplicationType @@ -3410,6 +3411,18 @@ Last Update (14.1.0.0)
+### OtherAudioSetting + +
UserSettings.AudioSettings.OtherAudioSetting + +``` + [0] = NoiseReduction + [1] = ProxVcReverbIntensity + [2] = SpatialAnnouncements +``` + +
+ ### OtherCondition
InventorySystem.Items.Firearms.Extensions.ConditionalEvaluator+OtherCondition @@ -3467,6 +3480,17 @@ Last Update (14.1.0.0)
+### PanelColliderId + +
AlphaWarheadNukesitePanel+PanelColliderId + +``` + [1] = Cancel + [2] = Lever +``` + +
+ ### ParameterMixingMode
InventorySystem.Items.Firearms.Attachments.ParameterMixingMode @@ -3641,6 +3665,7 @@ Last Update (14.1.0.0) [134217728] = FriendlyFireDetectorImmunity [268435456] = FriendlyFireDetectorTempDisable [536870912] = ServerLogLiveFeed + [1073741824] = ExecuteAs ```
@@ -3860,6 +3885,7 @@ Last Update (14.1.0.0) [1] = ConfirmThrowWeak [2] = ConfirmThrowFullForce [3] = CancelThrow + [4] = ForceCancel ``` @@ -5346,16 +5372,6 @@ Last Update (14.1.0.0) -### VcAudioSetting - -
UserSettings.AudioSettings.VcAudioSetting - -``` - [0] = NoiseReduction -``` - -
- ### VcMuteFlags
VoiceChat.VcMuteFlags @@ -5595,7 +5611,7 @@ Last Update (14.1.0.0)
Damage Handlers -```md title="Latest Updated: 14.1.0.0" +```md title="Latest Updated: 14.1.0.1" All available DamageHandlers + Symbol ':' literally means "inherits from" diff --git a/EXILED/docs/articles/contributing/index.md b/EXILED/docs/articles/contributing/index.md index fdcec6d5a0..b97036c9c0 100644 --- a/EXILED/docs/articles/contributing/index.md +++ b/EXILED/docs/articles/contributing/index.md @@ -6,7 +6,7 @@ title: Contributing to EXILED This is a simple tutorial guiding you to contribute to our framework. ### Forking EXILED -First, create a fork of our [GitHub repository](https://github.com/ExSLMod-Team/EXILED). +First, create a fork of our [GitHub repository](https://github.com/Exmod-Team/EXILED). Then, clone it to your computer like so: `git clone https://github.com/your-username/EXILED.git` @@ -14,7 +14,7 @@ Open a terminal in your forked EXILED folder and run ```git checkout dev```. Thi ### Setting `EXILED_REFERENCES` -If you haven't already, install the `SCP: Secret Laboratory Dedicated Server` through Steam or extract [this zip file](https://ExSLMod-Team.github.io/SL-References/Dev.zip) to an easily accessible folder. +If you haven't already, install the `SCP: Secret Laboratory Dedicated Server` through Steam or extract [this zip file](https://Exmod-Team.github.io/SL-References/Dev.zip) to an easily accessible folder. #### Windows users Open the Environment Variables menu by searching for `Environment Variables` in the Start Menu. diff --git a/EXILED/docs/articles/installation/automatic/linux.md b/EXILED/docs/articles/installation/automatic/linux.md index d8268593d6..e88fec515b 100644 --- a/EXILED/docs/articles/installation/automatic/linux.md +++ b/EXILED/docs/articles/installation/automatic/linux.md @@ -4,7 +4,7 @@ title: Automatic Linux Installation # Automatic Linux Installation -Download `Exiled.Installer-Linux` from [here](https://github.com/ExSLMod-Team/EXILED/releases). +Download `Exiled.Installer-Linux` from [here](https://github.com/Exmod-Team/EXILED/releases). Move it into your **server directory** and run it using `./Exiled.Installer-Linux` - Make sure the server directory is the one where LocalAdmin executable is found. diff --git a/EXILED/docs/articles/installation/automatic/windows.md b/EXILED/docs/articles/installation/automatic/windows.md index c9e890ea74..90126907c3 100644 --- a/EXILED/docs/articles/installation/automatic/windows.md +++ b/EXILED/docs/articles/installation/automatic/windows.md @@ -4,7 +4,7 @@ title: Automatic Windows Installation # Automatic Windows Installation -Download `Exiled.Installer-Win.exe` from [here](https://github.com/ExSLMod-Team/EXILED/releases). +Download `Exiled.Installer-Win.exe` from [here](https://github.com/Exmod-Team/EXILED/releases). Move it into your **server directory** and double click the .exe. - Make sure the server directory is the one where LocalAdmin.exe is found. diff --git a/EXILED/docs/articles/installation/manual.md b/EXILED/docs/articles/installation/manual.md index 9b8cb7c3d9..994aee045e 100644 --- a/EXILED/docs/articles/installation/manual.md +++ b/EXILED/docs/articles/installation/manual.md @@ -8,7 +8,7 @@ You can download exiled manually following this steps: ### Pick a release -You can select a release inside [our official GitHub repo](https://github.com/ExSLMod-Team/EXILED/releases/). +You can select a release inside [our official GitHub repo](https://github.com/Exmod-Team/EXILED/releases/). ### Download the release diff --git a/EXILED/docs/docfx.json b/EXILED/docs/docfx.json index 8dc6b28e44..872a0ab23d 100644 --- a/EXILED/docs/docfx.json +++ b/EXILED/docs/docfx.json @@ -27,7 +27,7 @@ "_enableSearch": true, "_lang": "en", "_gitContribute": { - "repo": "https://github.com/ExSLMod-Team/EXILED", + "repo": "https://github.com/Exmod-Team/EXILED", "branch": "dev" } }, diff --git a/EXILED/docs/toc.yml b/EXILED/docs/toc.yml index f3e6ee456a..eb1be93595 100644 --- a/EXILED/docs/toc.yml +++ b/EXILED/docs/toc.yml @@ -5,4 +5,4 @@ - name: NW Documentation href: articles/SCPSLRessources/NW_Documentation.html - name: Repository - href: https://github.com/ExSLMod-Team/EXILED + href: https://github.com/Exmod-Team/EXILED