From 431e389c62211fdaf4003a6e7c56cfb5bacf9ba3 Mon Sep 17 00:00:00 2001 From: Bolton <48883340+BoltonDev@users.noreply.github.com> Date: Sun, 18 May 2025 22:23:34 +0200 Subject: [PATCH 001/224] chore: update base game 14.1 (#527) * LabAPI Update (#435) * No error for build * Fix Exiled * Fix RoundEnding * Now is fucking separated by default * CustomSubtitles * Missing IsCustomAnnouncement * Fix 3 patch & removal of CustomHealthStat * Fix UsingAndCancellingItemUse * Fix TogglingFlashlight * La Flemme ? * La flemme 2 * Fix ReservedSlotPatch * Fix PickingUpItem(PickingUpAmmo) * Fix Hurt event * Fix FlippingCoin & EscapingPocketDimension * Fix FailingEscapePocketDimension * EscapingAndEscaped fix patch * Fix UpgradingPlayer patch * Fix BeingObserved * Fix PlacingTantrum * Fix FinishingRecall * ChangingCamera (Not working) * putting again the event than i was lazzy to fix * Rework InteractingDoor Patch missing removing the Prefix part lol & SkillIssueFix * Remove useless using and warning disable * OUPS * if stupidity was me it's here (i am stupid) * Fixing Noclip with breakingchange * CustomHumeShieldStat * Fix InteractingGenerator * Fix ChangingCamera * Not finish * InteractingDoor swap CanInteract & IsAllowed back to normal * Player::Gravity * Gravity in wrong place * IHumeShieldRole for Human * Fix BlastDoors & add OpenBlastDoor * Fix SpawningRagdoll Scale & missing argument when modify * Now fix in LabAPI only * we will keep the funny Ragdoll scale * Fix Merge & Split Command * new workflow for LabAPI * LabAPI Update 14.0.3 (#439) * No error for build * Fix Exiled * Fix RoundEnding * Now is fucking separated by default * CustomSubtitles * Missing IsCustomAnnouncement * Fix 3 patch & removal of CustomHealthStat * Fix UsingAndCancellingItemUse * Fix TogglingFlashlight * La Flemme ? * La flemme 2 * Fix ReservedSlotPatch * Fix PickingUpItem(PickingUpAmmo) * Fix Hurt event * Fix FlippingCoin & EscapingPocketDimension * Fix FailingEscapePocketDimension * EscapingAndEscaped fix patch * Fix UpgradingPlayer patch * Fix BeingObserved * Fix PlacingTantrum * Fix FinishingRecall * ChangingCamera (Not working) * putting again the event than i was lazzy to fix * Rework InteractingDoor Patch missing removing the Prefix part lol & SkillIssueFix * Remove useless using and warning disable * OUPS * if stupidity was me it's here (i am stupid) * Fixing Noclip with breakingchange * CustomHumeShieldStat * Fix InteractingGenerator * Fix ChangingCamera * Not finish * InteractingDoor swap CanInteract & IsAllowed back to normal * Player::Gravity * Gravity in wrong place * IHumeShieldRole for Human * Fix BlastDoors & add OpenBlastDoor * Fix SpawningRagdoll Scale & missing argument when modify * Now fix in LabAPI only * we will keep the funny Ragdoll scale * Fix Merge & Split Command * new workflow for LabAPI * NW_Documentation * SCP:SL LabAPI 14.0.3 * Fix One IL Error Patch (2 remain from previous update) * use Specific build for LabAPI * Miss updating this file * 9.6.0-beta3 * this will need to be revert * push_nuget not working for LabAPI * Fixing RoundEnd Patch * Adding CustomRole::Gravity * Remove Testing Log * fix: Command events fix (#444) command fixes * fix: Fixing UnlockingGenerator * Fix RoundEnd completelly * fix: Hurting event NRE fix (#443) fix: one day i'll kill person who made this Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * 9.6.0-beta4 * this will required to be revert * fix: InteractingDoorEvent::CanInteract (#463) * Fix InteractingDoorEvent::CanInteract * Oupsy * Fixed Error & InteractingScp330 IL Code * TODO: Fix THE CRASH ISSUE * Fix Consumed Event - doubt * Fix & Small change on UsingAndCancellingItemUse * Fix an issue where HashSetPool was never return * Fix IL CODE error & remove debug * Add warning in case of missing PrefabType * Capybara Toy * remove using * Fix SpawningRagdoll not setting NetworkInfo * Fix Ragdoll Scale & ConsumingEvent * Missing NetworkInfo.Scale Argument * Fix: Door.Get(GameObject) * TantrumHazard is always null * 9.6.0-beta5 * Update EXILED_DLL_ARCHIVER_URL for LabAPI * fix: use basegame validation for CustomInfo (#452) * Validate using basegame * Update EXILED/Exiled.API/Features/Player.cs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * feat: Custom limits for armor & virtual dmg for customweapon (#477) * fix: activating warhead panel event fix (#504) fix: fix for activating warhead panel * Fix Error (from Conflict) * Fix doc error * Fix few error * refactor!: update to 14.1 (#508) * refactor!: update to 14.1 * fix: few patches fix * feat: new custom keycard logic * fix: ActivatingWarheadPanel event * fix: checkpoint sequence time * fix: Shot event * fix: DroppingCandy event * Fix last Error on Release Build * Update new PrefabType * fix: AnnouncingScpTermination event * fix: SpawningItem event error (require testing) * fix: Shot.cs fix * analysis error * Fix error * Fix build error * NW_Documentation * fix: announcing wave entrance * Reference new Room * Fix Build error * fix: fixes pickingup event * Nameless Skill Issue * fix: locker patch * fix: spawning room connector * CameraToy * Get CameraToy * Finish InteractableToy & fix doc * fix Error for build * Remove todo * fix: generator events * ElevatorType.ServerRoom * Fix a 2 hours loss * Fix NW moment * fix: interacting generator * fix: interacting locker event * fix: missing obstacle shot * fix: KeycardInteracting again * Fix SendingCassieMessage * fix: temp fix interacting door (need to refactor) * refactor: change the property * fix doc * extension for Scp127 & ServerRoom * FirearmType.Scp127 * AdminToyList * fix: camera weren't added * new CameraType * wrong doc * fix: interacting door event (improved) * Invalid Camera * Better doc * fix: interacting door (bolton issue) * FixDamagingWindow index * RemoveBreakingChange * Fix FailingEscapePocketDimensionEventArgs --------- Co-authored-by: Bolton <48883340+BoltonDev@users.noreply.github.com> Co-authored-by: VALERA771 Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> * 9.6.0-beta6 * SNOWBALL DO NOT EXIST * fix: ChangingCamera event (#511) * fix: useless fix now & prevent crash (#512) * fix: EndingRound event IsAllowed (#513) fix: EndingRound event isallowed * bump: 9.6.0-beta7 * fix: few fixes (#514) * fix: keycard interacting * fix: EscapingEvent IsAllowed * fix: command type invalid * fix: landing event * fix: EscapingPocketDimension event * fix: Handcuffing / RemovingHandcuff events * fix: sentvalidcommand event * bump: 9.6.0-beta8 * fix: fix AutoUpdater Directory finder (#515) * Update Capybara.cs * Revert "Update Capybara.cs" This reverts commit 6ea4789dc4d8cb8bb7f429281b930133bc9be62c. * fix autoupdater * ok now its fixed 100% * fix: custom weapons not doing default damage when set to below 0 (#518) * fix custom weapons not doing default damage when set to below 0 * make default damage -1 * Workflow fix (#3) * v9.5.2 * Update release.yml * Update main.yml * update nuget * update docs * update dev * Fixing Error & Removing Property that will not be implmented * EffectType.Scp1344Detected * Update labapi.yml * Update labapi.yml * TextToy Implementation API * chore: remove hub (doesn't exist anymore) * fix: update exmod -> exslmod & bump: 9.6.0 * fix: missing some change * missing hub --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: Yamato Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> Co-authored-by: SlejmUr Co-authored-by: VALERA771 Co-authored-by: Mike <146554836+MikeSus1@users.noreply.github.com> Co-authored-by: TtroubleTT <121741230+TtroubleTT@users.noreply.github.com> --- .github/workflows/dev.yml | 4 +- .github/workflows/docs.yml | 2 +- .github/workflows/labapi.yml | 74 +++ .github/workflows/main.yml | 2 +- .github/workflows/push_nuget.yml | 2 +- .github/workflows/release.yml | 4 +- EXILED/EXILED.props | 6 +- EXILED/Exiled.API/Enums/AdminToyType.cs | 20 + EXILED/Exiled.API/Enums/CameraType.cs | 14 + EXILED/Exiled.API/Enums/DamageType.cs | 5 + EXILED/Exiled.API/Enums/DoorType.cs | 19 + EXILED/Exiled.API/Enums/EffectType.cs | 5 + EXILED/Exiled.API/Enums/ElevatorType.cs | 5 + EXILED/Exiled.API/Enums/FirearmType.cs | 5 + EXILED/Exiled.API/Enums/GeneratorState.cs | 4 +- EXILED/Exiled.API/Enums/GlassType.cs | 5 + EXILED/Exiled.API/Enums/LockerType.cs | 5 + EXILED/Exiled.API/Enums/PrefabType.cs | 36 ++ EXILED/Exiled.API/Enums/RoomType.cs | 10 + EXILED/Exiled.API/Exiled.API.csproj | 2 +- .../Extensions/DamageTypeExtensions.cs | 1 + .../Extensions/DoorTypeExtensions.cs | 2 +- .../Extensions/EffectTypeExtension.cs | 1 + .../Exiled.API/Extensions/ItemExtensions.cs | 2 + .../Exiled.API/Extensions/LockerExtensions.cs | 1 + .../Exiled.API/Extensions/RoomExtensions.cs | 2 +- .../Extensions/UserGroupExtensions.cs | 4 +- EXILED/Exiled.API/Features/Camera.cs | 21 +- .../Exiled.API/Features/CustomHealthStat.cs | 26 - .../CustomStats/CustomHumeShieldStat.cs | 25 +- .../DamageHandlers/GenericDamageHandler.cs | 3 + EXILED/Exiled.API/Features/Doors/BasicDoor.cs | 6 +- .../Features/Doors/CheckpointDoor.cs | 18 +- EXILED/Exiled.API/Features/Doors/Door.cs | 34 +- .../Exiled.API/Features/Doors/ElevatorDoor.cs | 3 +- EXILED/Exiled.API/Features/Generator.cs | 14 +- .../Features/Hazards/TemporaryHazard.cs | 4 +- EXILED/Exiled.API/Features/Items/Armor.cs | 17 +- EXILED/Exiled.API/Features/Items/Keycard.cs | 45 +- EXILED/Exiled.API/Features/Items/Scp1344.cs | 2 +- EXILED/Exiled.API/Features/Lockers/Chamber.cs | 26 +- EXILED/Exiled.API/Features/Map.cs | 2 +- EXILED/Exiled.API/Features/Npc.cs | 13 +- EXILED/Exiled.API/Features/Paths.cs | 4 +- .../Pickups/ExplosiveGrenadePickup.cs | 4 +- .../Features/Pickups/KeycardPickup.cs | 17 +- .../Projectiles/ExplosionGrenadeProjectile.cs | 8 +- EXILED/Exiled.API/Features/Player.cs | 98 ++-- EXILED/Exiled.API/Features/Ragdoll.cs | 23 +- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 27 +- EXILED/Exiled.API/Features/Roles/HumanRole.cs | 8 +- .../Exiled.API/Features/Roles/Scp049Role.cs | 38 +- .../Exiled.API/Features/Roles/Scp079Role.cs | 2 +- EXILED/Exiled.API/Features/Room.cs | 23 +- EXILED/Exiled.API/Features/Round.cs | 2 +- EXILED/Exiled.API/Features/Scp914.cs | 8 +- EXILED/Exiled.API/Features/Server.cs | 6 +- EXILED/Exiled.API/Features/Toys/AdminToy.cs | 4 + EXILED/Exiled.API/Features/Toys/CameraToy.cs | 83 +++ EXILED/Exiled.API/Features/Toys/Capybara.cs | 45 ++ .../Features/Toys/InteractableToy.cs | 61 +++ EXILED/Exiled.API/Features/Toys/Light.cs | 2 + EXILED/Exiled.API/Features/Toys/Text.cs | 55 ++ EXILED/Exiled.API/Features/Warhead.cs | 17 +- EXILED/Exiled.API/Features/Window.cs | 3 +- .../Exiled.CreditTags.csproj | 4 +- .../Features/DatabaseHandler.cs | 2 +- .../API/EventArgs/OwnerEscapingEventArgs.cs | 2 +- .../API/Features/CustomArmor.cs | 19 + .../API/Features/CustomKeycard.cs | 84 ++- .../API/Features/CustomWeapon.cs | 4 +- .../Exiled.CustomItems.csproj | 2 +- .../API/Features/CustomRole.cs | 9 +- .../Exiled.CustomRoles.csproj | 2 +- EXILED/Exiled.Events/Commands/Config/Merge.cs | 2 +- EXILED/Exiled.Events/Commands/Config/Split.cs | 2 +- EXILED/Exiled.Events/Commands/Hub/Hub.cs | 51 -- .../Commands/Hub/HubApi/ApiProvider.cs | 64 --- .../Commands/Hub/HubApi/Models/HubPlugin.cs | 35 -- EXILED/Exiled.Events/Commands/Hub/Install.cs | 118 ----- EXILED/Exiled.Events/Config.cs | 1 + .../Cassie/SendingCassieMessageEventArgs.cs | 20 +- .../Map/AnnouncingScpTerminationEventArgs.cs | 9 +- .../EventArgs/Map/FillingLockerEventArgs.cs | 2 +- .../Player/ActivatingGeneratorEventArgs.cs | 7 +- .../Player/ActivatingWarheadPanelEventArgs.cs | 2 +- .../Player/CancelledItemUseEventArgs.cs | 13 +- .../Player/CancellingItemUseEventArgs.cs | 8 +- .../Player/ClosingGeneratorEventArgs.cs | 5 +- .../DeactivatingWorkstationEventArgs.cs | 2 +- .../EventArgs/Player/EscapingEventArgs.cs | 8 +- .../EscapingPocketDimensionEventArgs.cs | 15 +- .../FailingEscapePocketDimensionEventArgs.cs | 16 +- .../EventArgs/Player/FlippingCoinEventArgs.cs | 6 +- .../EventArgs/Player/HurtEventArgs.cs | 8 +- .../Player/InteractingDoorEventArgs.cs | 7 +- .../Player/OpeningGeneratorEventArgs.cs | 7 +- .../Player/PickingUpItemEventArgs.cs | 6 +- .../Player/PreAuthenticatingEventArgs.cs | 1 - .../Player/ReservedSlotsCheckEventArgs.cs | 42 +- .../Player/SearchingPickupEventArgs.cs | 4 +- .../Player/SendingValidCommandEventArgs.cs | 2 +- .../Player/SentValidCommandEventArgs.cs | 2 +- .../EventArgs/Player/ShotEventArgs.cs | 5 +- .../Player/SpawningRagdollEventArgs.cs | 25 +- .../Player/StoppingGeneratorEventArgs.cs | 5 +- .../Player/TogglingNoClipEventArgs.cs | 10 +- .../Player/UnlockingGeneratorEventArgs.cs | 2 +- .../EventArgs/Player/UsingItemEventArgs.cs | 8 +- .../Player/UsingRadioBatteryEventArgs.cs | 7 +- .../Scp049/ActivatingSenseEventArgs.cs | 4 +- .../EventArgs/Scp049/SendingCallEventArgs.cs | 2 +- .../Scp0492/ConsumingCorpseEventArgs.cs | 4 +- .../Scp173/PlacingTantrumEventArgs.cs | 5 + .../Scp330/InteractingScp330EventArgs.cs | 28 +- .../EventArgs/Server/EndingRoundEventArgs.cs | 15 +- EXILED/Exiled.Events/Events.cs | 10 +- EXILED/Exiled.Events/Exiled.Events.csproj | 2 +- .../Handlers/Internal/AdminToyList.cs | 27 + .../Handlers/Internal/ClientStarted.cs | 25 +- .../Handlers/Internal/SceneUnloaded.cs | 1 - EXILED/Exiled.Events/Handlers/Player.cs | 4 - .../Events/Cassie/SendingCassieMessage.cs | 47 +- .../Patches/Events/Item/KeycardInteracting.cs | 144 ++--- .../Events/Map/AnnouncingChaosEntrance.cs | 2 +- .../Events/Map/AnnouncingNtfEntrance.cs | 6 +- .../Events/Map/AnnouncingScpTermination.cs | 10 +- .../Events/Map/ExplodingFlashGrenade.cs | 2 +- .../Events/Map/ExplodingFragGrenade.cs | 95 ++-- .../Patches/Events/Map/SpawningItem.cs | 4 +- .../Events/Map/SpawningRoomConnector.cs | 10 +- .../Events/Player/ActivatingWarheadPanel.cs | 74 +-- .../Events/Player/ChangingRoleAndSpawned.cs | 2 - .../Events/Player/DamagingShootingTarget.cs | 2 +- .../Patches/Events/Player/DamagingWindow.cs | 2 +- .../Events/Player/EscapingAndEscaped.cs | 60 +-- .../Events/Player/EscapingPocketDimension.cs | 46 +- .../Player/FailingEscapePocketDimension.cs | 30 +- .../Patches/Events/Player/FlippingCoin.cs | 11 +- .../Patches/Events/Player/Hurting.cs | 23 +- .../Patches/Events/Player/InteractingDoor.cs | 200 ++++--- .../Events/Player/InteractingGenerator.cs | 270 +++------- .../Events/Player/InteractingLocker.cs | 10 +- .../Patches/Events/Player/Landing.cs | 2 +- .../Patches/Events/Player/PickingUp330.cs | 7 +- .../Patches/Events/Player/PickingUpAmmo.cs | 26 +- .../Patches/Events/Player/PickingUpArmor.cs | 13 +- .../Patches/Events/Player/PickingUpItem.cs | 11 +- .../Patches/Events/Player/PickingUpScp244.cs | 14 +- .../Events/Player/ProcessDisarmMessage.cs | 8 +- .../Events/Player/ReservedSlotPatch.cs | 86 +-- .../Player/SendingValidGameConsoleCommand.cs | 106 ++-- .../Events/Player/SendingValidRACommand.cs | 36 +- .../Patches/Events/Player/Shot.cs | 160 +++--- .../Patches/Events/Player/SpawningRagdoll.cs | 19 +- .../Events/Player/TogglingFlashlight.cs | 28 +- .../Patches/Events/Player/TogglingNoClip.cs | 48 +- .../Patches/Events/Player/TogglingRadio.cs | 4 +- .../Player/UsingAndCancellingItemUse.cs | 45 +- .../Events/Player/UsingRadioBattery.cs | 36 +- .../Patches/Events/Scp049/FinishingRecall.cs | 4 +- .../Patches/Events/Scp0492/Consumed.cs | 19 +- .../Patches/Events/Scp0492/Consuming.cs | 15 +- .../Patches/Events/Scp079/ChangingCamera.cs | 22 +- .../Patches/Events/Scp173/BeingObserved.cs | 13 +- .../Patches/Events/Scp173/PlacingTantrum.cs | 14 +- .../Patches/Events/Scp330/DroppingCandy.cs | 35 +- .../Events/Scp330/InteractingScp330.cs | 132 ++--- .../Patches/Events/Scp914/UpgradingPlayer.cs | 23 +- .../Patches/Events/Server/Reporting.cs | 10 +- .../Patches/Events/Server/RoundEnd.cs | 64 +-- .../Patches/Fixes/ArmorDropPatch.cs | 55 -- .../Patches/Fixes/FixNWDisarmCommand.cs | 48 -- .../Patches/Fixes/LockerFixes.cs | 10 +- .../Patches/Fixes/NWFixDetonationTimer.cs | 2 +- ...RemoteAdminNpcCommandAddToDictionaryFix.cs | 1 + .../Patches/Fixes/Scp3114FriendlyFireFix.cs | 6 +- .../Patches/Generic/CameraList.cs | 5 +- .../Patches/Generic/LockerList.cs | 13 +- .../Patches/Generic/OfflineModeIds.cs | 9 +- .../Exiled.Events/Patches/Generic/RoomList.cs | 9 +- .../Patches/Generic/ServerNamePatch.cs | 2 +- EXILED/Exiled.Example/Events/PlayerHandler.cs | 2 +- EXILED/Exiled.Example/Exiled.Example.csproj | 2 +- EXILED/Exiled.Installer/Program.cs | 2 +- EXILED/Exiled.Installer/README.md | 4 +- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- EXILED/Exiled.Loader/Config.cs | 2 +- EXILED/Exiled.Loader/ConfigManager.cs | 2 +- EXILED/Exiled.Loader/Exiled.Loader.csproj | 2 +- EXILED/Exiled.Loader/Loader.cs | 2 +- EXILED/Exiled.Loader/LoaderPlugin.cs | 64 ++- EXILED/Exiled.Loader/Updater.cs | 6 +- .../Exiled.Permissions.csproj | 2 +- .../Extensions/Permissions.cs | 4 +- EXILED/Exiled/Exiled.nuspec | 8 +- .../SCPSLRessources/NW_Documentation.md | 500 ++++++++++++++++-- EXILED/docs/articles/contributing/index.md | 4 +- .../articles/installation/automatic/linux.md | 2 +- .../installation/automatic/windows.md | 2 +- EXILED/docs/articles/installation/manual.md | 2 +- EXILED/docs/docfx.json | 2 +- EXILED/docs/docs.csproj | 2 +- EXILED/docs/toc.yml | 4 +- 204 files changed, 2496 insertions(+), 2157 deletions(-) create mode 100644 .github/workflows/labapi.yml delete mode 100644 EXILED/Exiled.API/Features/CustomHealthStat.cs create mode 100644 EXILED/Exiled.API/Features/Toys/CameraToy.cs create mode 100644 EXILED/Exiled.API/Features/Toys/Capybara.cs create mode 100644 EXILED/Exiled.API/Features/Toys/InteractableToy.cs create mode 100644 EXILED/Exiled.API/Features/Toys/Text.cs delete mode 100644 EXILED/Exiled.Events/Commands/Hub/Hub.cs delete mode 100644 EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs delete mode 100644 EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs delete mode 100644 EXILED/Exiled.Events/Commands/Hub/Install.cs create mode 100644 EXILED/Exiled.Events/Handlers/Internal/AdminToyList.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/ArmorDropPatch.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixNWDisarmCommand.cs diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 7ee77d48a0..ab7b2d0556 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -16,9 +16,9 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip + EXILED_REFERENCES_URL: https://exslmod-team.github.io/SL-References/Dev.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References - EXILED_DLL_ARCHIVER_URL: https://github.com/ExMod-Team/EXILED-DLL-Archiver/releases/latest/download/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/ExSLMod-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 f4d75022e9..728da76f7f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ permissions: id-token: write env: - EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Master.zip + EXILED_REFERENCES_URL: https://exslmod-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 new file mode 100644 index 0000000000..69cb71f0aa --- /dev/null +++ b/.github/workflows/labapi.yml @@ -0,0 +1,74 @@ +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 3c842e3a39..576a58f46b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Master.zip + EXILED_REFERENCES_URL: https://exslmod-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 5150b0c7ec..29175ffee8 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://ExMod-Team.github.io/SL-References/Dev.zip + EXILED_REFERENCES_URL: https://exslmod-team.github.io/SL-References/LabAPI.zip EXILED_REFERENCES_PATH: ${{ github.workspace }}/EXILED/References jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac0937bd69..9b1b16ee05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,9 @@ defaults: working-directory: ./EXILED env: - EXILED_REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip + 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/ExMod-Team/EXILED-DLL-Archiver/releases/latest/download/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/ExSLMod-Team/EXILED-DLL-Archiver/releases/download/v1.8.1/EXILED-DLL-Archiver.exe jobs: build: diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 53be61447a..eefc8a1140 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.5.2 + 9.6.0 false @@ -25,8 +25,8 @@ Copyright © $(Authors) 2020 - $([System.DateTime]::Now.ToString("yyyy")) Git - https://github.com/ExMod-Team/EXILED - https://github.com/ExMod-Team/EXILED + https://github.com/ExSLMod-Team/EXILED + https://github.com/ExSLMod-Team/EXILED CC-BY-SA-3.0 $(DefineConstants);PUBLIC_BETA diff --git a/EXILED/Exiled.API/Enums/AdminToyType.cs b/EXILED/Exiled.API/Enums/AdminToyType.cs index c7f937721a..0ad8bb43dd 100644 --- a/EXILED/Exiled.API/Enums/AdminToyType.cs +++ b/EXILED/Exiled.API/Enums/AdminToyType.cs @@ -32,5 +32,25 @@ public enum AdminToyType /// Speaker toy. /// Speaker, + + /// + /// Capybara toy. + /// + Capybara, + + /// + /// InvisibleInteractable toy. + /// + InvisibleInteractableToy, + + /// + /// Camera Object toy. + /// + CameraToy, + + /// + /// Text toy. + /// + TextToy, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/CameraType.cs b/EXILED/Exiled.API/Enums/CameraType.cs index 80c1b7cb89..8b064b045f 100644 --- a/EXILED/Exiled.API/Enums/CameraType.cs +++ b/EXILED/Exiled.API/Enums/CameraType.cs @@ -123,6 +123,7 @@ public enum CameraType Hcz173ContChamber, [System.Obsolete("This Camera no longer exist.")] Hcz173Hallway, + [System.Obsolete("This Camera no longer exist.")] HczCurve, HczJunkMain, HczJunkHallway, @@ -137,6 +138,19 @@ public enum CameraType HczWarheadPortElevator, HczMicroHIDLab, HczPipesMain, + HczScp127Lab, + HczScp127Containment, + HczServersUpperStorage, + HczLowerServerStorage, + HczServerStaircase, + #endregion + + #region custom + EzArmCameraToy, + EzCameraToy, + HczCameraToy, + LczCameraToy, + SzCameraToy, #endregion } } diff --git a/EXILED/Exiled.API/Enums/DamageType.cs b/EXILED/Exiled.API/Enums/DamageType.cs index 602ad2f740..ae93c5c020 100644 --- a/EXILED/Exiled.API/Enums/DamageType.cs +++ b/EXILED/Exiled.API/Enums/DamageType.cs @@ -269,5 +269,10 @@ public enum DamageType /// Damage caused by . /// SnowBall, + + /// + /// Damage caused by . + /// + Scp127, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index ee5655f77e..4e3cb644f5 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -167,11 +167,20 @@ public enum DoorType /// /// Represents the HID_UPPER door. /// + [Obsolete("This Door has been renamed too HID_LAB.")] HIDUpper, + /// + /// Represents the HID_LAB door. + /// +#pragma warning disable CS0618 + HIDLab = HIDUpper, +#pragma warning restore CS0618 + /// /// Represents the HID_LOWER door. /// + [Obsolete("This Door has been removed from the game.")] HIDLower, /// @@ -320,5 +329,15 @@ public enum DoorType /// Represents the ESCAPE_FINAL door. /// EscapeFinal, + + /// + /// Represents the Elevator door for . + /// + ElevatorServerRoom, + + /// + /// Represents the HCZ_127_LAB door. + /// + Hcz127Lab, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index 8ac82586aa..b32ba684d8 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -283,5 +283,10 @@ public enum EffectType /// [Obsolete("Only availaible for Christmas and AprilFools.")] Snowed, + + /// + /// . + /// + Scp1344Detected, } } diff --git a/EXILED/Exiled.API/Enums/ElevatorType.cs b/EXILED/Exiled.API/Enums/ElevatorType.cs index 02783b7180..ebccf5b356 100644 --- a/EXILED/Exiled.API/Enums/ElevatorType.cs +++ b/EXILED/Exiled.API/Enums/ElevatorType.cs @@ -48,5 +48,10 @@ public enum ElevatorType : byte /// Light Containment Zone checkpoint B elevator. /// LczB, + + /// + /// Heavy Containment Zone ServerRoom elevator. + /// + ServerRoom, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/FirearmType.cs b/EXILED/Exiled.API/Enums/FirearmType.cs index aa814136fa..05cccf2388 100644 --- a/EXILED/Exiled.API/Enums/FirearmType.cs +++ b/EXILED/Exiled.API/Enums/FirearmType.cs @@ -89,5 +89,10 @@ public enum FirearmType /// Represents the . /// A7, + + /// + /// Represents the . + /// + Scp127, } } diff --git a/EXILED/Exiled.API/Enums/GeneratorState.cs b/EXILED/Exiled.API/Enums/GeneratorState.cs index 1b434908b0..4f0a9b7111 100644 --- a/EXILED/Exiled.API/Enums/GeneratorState.cs +++ b/EXILED/Exiled.API/Enums/GeneratorState.cs @@ -20,9 +20,9 @@ namespace Exiled.API.Enums public enum GeneratorState : byte { /// - /// Generator is locked. + /// Generator is doing nothing. /// - None = 1, + None = 0, /// /// Generator is unlocked. diff --git a/EXILED/Exiled.API/Enums/GlassType.cs b/EXILED/Exiled.API/Enums/GlassType.cs index 9857ed2a82..4144c7fd98 100644 --- a/EXILED/Exiled.API/Enums/GlassType.cs +++ b/EXILED/Exiled.API/Enums/GlassType.cs @@ -69,5 +69,10 @@ public enum GlassType /// Represents the window in . /// TestRoom, + + /// + /// Represents the window in . + /// + Scp127, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/LockerType.cs b/EXILED/Exiled.API/Enums/LockerType.cs index af62a06cb3..14c2f365df 100644 --- a/EXILED/Exiled.API/Enums/LockerType.cs +++ b/EXILED/Exiled.API/Enums/LockerType.cs @@ -115,5 +115,10 @@ public enum LockerType /// SCP-1853 pedestal. /// Scp1853Pedestal, + + /// + /// SCP-127 pedestal. + /// + Scp127Pedestal, } } diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index 7ec8a851c3..f34093aba8 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -336,5 +336,41 @@ public enum PrefabType [Prefab(912031041, "ElevatorChamberNuke")] ElevatorChamberNuke, + + [Prefab(3087007600, "CapybaraToy")] + CapybaraToy, + + [Prefab(3539746802, "Sinkhole")] + Sinkhole, + + [Prefab(1548138668, "AutoRagdoll")] + AutoRagdoll, + + [Prefab(1323017091, "ElevatorChamberCargo")] + ElevatorChamberCargo, + + [Prefab(359728307, "InvisibleInteractableToy")] + InvisibleInteractableToy, + + [Prefab(1824808402, "EzArmCameraToy")] + EzArmCameraToy, + + [Prefab(3375932423, "EzCameraToy")] + EzCameraToy, + + [Prefab(144958943, "HczCameraToy")] + HczCameraToy, + + [Prefab(2026969629, "LczCameraToy")] + LczCameraToy, + + [Prefab(1548138668, "SzCameraToy")] + SzCameraToy, + + [Prefab(2842703865, "KeycardPickup_Chaos")] + KeycardPickupChaos, + + [Prefab(162530276, "TextToy")] + TextToy, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/RoomType.cs b/EXILED/Exiled.API/Enums/RoomType.cs index 3cb78e53f2..6e236c25d1 100644 --- a/EXILED/Exiled.API/Enums/RoomType.cs +++ b/EXILED/Exiled.API/Enums/RoomType.cs @@ -328,5 +328,15 @@ public enum RoomType /// Entrance Zone's straight hall with Dr.L's and conference room 9b locked room. /// EzSmallrooms, + + /// + /// Heavy Containment Zone's SCP-330 room. + /// + Hcz127, + + /// + /// Heavy Containment Zone's storage / server room. + /// + HczServerRoom, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Exiled.API.csproj b/EXILED/Exiled.API/Exiled.API.csproj index 961b44e98d..c8737a790c 100644 --- a/EXILED/Exiled.API/Exiled.API.csproj +++ b/EXILED/Exiled.API/Exiled.API.csproj @@ -27,10 +27,10 @@ + - diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 7e496e77ac..6abeea8a2f 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -102,6 +102,7 @@ public static class DamageTypeExtensions { ItemType.Jailbird, DamageType.Jailbird }, { ItemType.GunFRMG0, DamageType.Frmg0 }, { ItemType.GunA7, DamageType.A7 }, + { ItemType.GunSCP127, DamageType.Scp127 }, }; /// diff --git a/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs index 79164df2b3..a4380d81cf 100644 --- a/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs @@ -35,6 +35,6 @@ public static bool IsGate(this DoorType door) => door is DoorType.GateA or DoorT /// The door to be checked. /// Returns whether the is an elevator. public static bool IsElevator(this DoorType door) => door is DoorType.ElevatorGateA or DoorType.ElevatorGateB - or DoorType.ElevatorLczA or DoorType.ElevatorLczB or DoorType.ElevatorNuke or DoorType.ElevatorScp049; + or DoorType.ElevatorLczA or DoorType.ElevatorLczB or DoorType.ElevatorNuke or DoorType.ElevatorScp049 or DoorType.ElevatorServerRoom; } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index dc209c3ba0..6f038fd761 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -75,6 +75,7 @@ public static class EffectTypeExtension { EffectType.SeveredEyes, typeof(SeveredEyes) }, { EffectType.PitDeath, typeof(PitDeath) }, { EffectType.Blurred, typeof(Blurred) }, + { EffectType.Scp1344Detected, typeof(Scp1344Detected) }, #pragma warning disable CS0618 { EffectType.Marshmallow, typeof(MarshmallowEffect) }, { EffectType.BecomingFlamingo, typeof(BecomingFlamingo) }, diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs index c169f03011..458fd10bc4 100644 --- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs +++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs @@ -182,6 +182,7 @@ public static int GetMaxAmmo(this FirearmType item) ItemType.GunCom45 => FirearmType.Com45, ItemType.GunFRMG0 => FirearmType.FRMG0, ItemType.ParticleDisruptor => FirearmType.ParticleDisruptor, + ItemType.GunSCP127 => FirearmType.Scp127, _ => FirearmType.None, }; @@ -220,6 +221,7 @@ public static int GetMaxAmmo(this FirearmType item) FirearmType.Com45 => ItemType.GunCom45, FirearmType.FRMG0 => ItemType.GunFRMG0, FirearmType.ParticleDisruptor => ItemType.ParticleDisruptor, + FirearmType.Scp127 => ItemType.GunSCP127, _ => ItemType.None, }; diff --git a/EXILED/Exiled.API/Extensions/LockerExtensions.cs b/EXILED/Exiled.API/Extensions/LockerExtensions.cs index 4a6466f636..da8b915794 100644 --- a/EXILED/Exiled.API/Extensions/LockerExtensions.cs +++ b/EXILED/Exiled.API/Extensions/LockerExtensions.cs @@ -48,6 +48,7 @@ public static class LockerExtensions "RegularMedkitStructure" => LockerType.Medkit, "AdrenalineMedkitStructure" => LockerType.Adrenaline, "MicroHIDpedestal" => LockerType.MicroHid, + "SCP_127_Container" => LockerType.Scp127Pedestal, _ => LockerType.Unknown, }; } diff --git a/EXILED/Exiled.API/Extensions/RoomExtensions.cs b/EXILED/Exiled.API/Extensions/RoomExtensions.cs index 14141a4422..8d36486505 100644 --- a/EXILED/Exiled.API/Extensions/RoomExtensions.cs +++ b/EXILED/Exiled.API/Extensions/RoomExtensions.cs @@ -52,7 +52,7 @@ public static bool IsCheckpoint(this RoomType room) => room is RoomType.LczCheck /// Returns whether the contains any SCP. public static bool IsScp(this RoomType room) => room is RoomType.Lcz173 or RoomType.Lcz330 or RoomType.Lcz914 or RoomType.Hcz049 or RoomType.Hcz079 or - RoomType.Hcz096 or RoomType.Hcz106 or RoomType.Hcz939; + RoomType.Hcz096 or RoomType.Hcz106 or RoomType.Hcz939 or RoomType.Hcz127; /// /// Converts the provided into the corresponding . diff --git a/EXILED/Exiled.API/Extensions/UserGroupExtensions.cs b/EXILED/Exiled.API/Extensions/UserGroupExtensions.cs index 7b9138618c..41b8523433 100644 --- a/EXILED/Exiled.API/Extensions/UserGroupExtensions.cs +++ b/EXILED/Exiled.API/Extensions/UserGroupExtensions.cs @@ -37,7 +37,7 @@ public static bool EqualsTo(this UserGroup @this, UserGroup other) /// /// The . /// The key of that group, or if not found. - public static string GetKey(this UserGroup @this) => Server.PermissionsHandler._groups + public static string GetKey(this UserGroup @this) => Server.PermissionsHandler.Groups .FirstOrDefault(pair => pair.Value.EqualsTo(@this)).Key; /// @@ -47,7 +47,7 @@ public static string GetKey(this UserGroup @this) => Server.PermissionsHandler._ /// The value of that group, or if not found. public static UserGroup GetValue(string groupName) { - ServerStatic.GetPermissionsHandler().GetAllGroups().TryGetValue(groupName, out UserGroup userGroup); + ServerStatic.PermissionsHandler.GetAllGroups().TryGetValue(groupName, out UserGroup userGroup); return userGroup; } } diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index c794defcd5..da3f73e27b 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -70,7 +70,6 @@ public class Camera : IWrapper, IWorldSpace ["HCZ ARMORY"] = CameraType.HczArmory, ["HCZ ARMORY INTERIOR"] = CameraType.HczArmoryInterior, ["HCZ CROSSING"] = CameraType.HczCrossing, - ["HCZ CURVE"] = CameraType.HczCurve, ["HCZ ELEV SYS A"] = CameraType.HczElevSysA, ["HCZ ELEV SYS B"] = CameraType.HczElevSysB, ["HCZ HALLWAY"] = CameraType.HczHallway, @@ -134,6 +133,18 @@ public class Camera : IWrapper, IWorldSpace ["WARHEAD TOP ELEVATORS"] = CameraType.HczWarheadTopElevators, ["WARHEAD CONNECTOR"] = CameraType.HczWarheadConnector, ["WARHEAD PORT ELEVATOR"] = CameraType.HczWarheadPortElevator, + ["HCZ SCP-127 LAB"] = CameraType.HczScp127Lab, + ["HCZ SCP-127 CONTAINMENT"] = CameraType.HczScp127Containment, + ["HCZ SERVERS UPPER STORAGE"] = CameraType.HczServersUpperStorage, + ["HCZ LOWER SERVER STORAGE"] = CameraType.HczLowerServerStorage, + ["HCZ SERVERS STAIRCASE"] = CameraType.HczServerStaircase, + + // CustomCamera + ["EZ ARM CAMERA TOY"] = CameraType.EzArmCameraToy, + ["EZ CAMERA TOY"] = CameraType.EzCameraToy, + ["HCZ CAMERA TOY"] = CameraType.HczCameraToy, + ["LCZ CAMERA TOY"] = CameraType.LczCameraToy, + ["SZ CAMERA TOY"] = CameraType.SzCameraToy, }; private Room room; @@ -147,10 +158,6 @@ internal Camera(Scp079Camera camera079) Base = camera079; Camera079ToCamera.Add(camera079, this); Type = GetCameraType(); -#if DEBUG - if (Type is CameraType.Unknown) - Log.Error($"[CAMERATYPE UNKNOWN] {this} BASE = {Base}"); -#endif } /// @@ -214,8 +221,8 @@ internal Camera(Scp079Camera camera079) /// public Quaternion Rotation { - get => Base._cameraAnchor.rotation; - set => Base._cameraAnchor.rotation = value; + get => Base.CameraAnchor.rotation; + set => Base.CameraAnchor.rotation = value; } /// diff --git a/EXILED/Exiled.API/Features/CustomHealthStat.cs b/EXILED/Exiled.API/Features/CustomHealthStat.cs deleted file mode 100644 index 57f6d31c20..0000000000 --- a/EXILED/Exiled.API/Features/CustomHealthStat.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.API.Features -{ - using PlayerStatsSystem; - - /// - /// A custom version of which allows the player's max amount of health to be changed. - /// TODO: Move to Features.CustomStats. - /// - public class CustomHealthStat : HealthStat - { - /// - public override float MaxValue => CustomMaxValue == default ? base.MaxValue : CustomMaxValue; - - /// - /// Gets or sets the maximum amount of health the player will have. - /// - public float CustomMaxValue { get; set; } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs b/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs index 78c4cd807d..e19ce970f5 100644 --- a/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs +++ b/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -18,25 +18,24 @@ namespace Exiled.API.Features.CustomStats /// public class CustomHumeShieldStat : HumeShieldStat { - /// - public override float MaxValue => CustomMaxValue == -1 ? base.MaxValue : CustomMaxValue; - /// /// Gets or sets the multiplier for gaining HumeShield. /// public float ShieldRegenerationMultiplier { get; set; } = 1; - /// - /// Gets or sets the maximum amount of HumeShield the player can have. - /// - public float CustomMaxValue { get; set; } = -1; - - private float ShieldRegeneration => TryGetHsModule(out HumeShieldModuleBase controller) ? controller.HsRegeneration * ShieldRegenerationMultiplier : 0; + private float ShieldRegeneration + { + get + { + IHumeShieldProvider.GetForHub(Hub, out _, out _, out float hsRegen, out _); + return hsRegen * ShieldRegenerationMultiplier; + } + } /// public override void Update() { - if (MaxValue == -1 && ShieldRegenerationMultiplier is 1) + if (ShieldRegenerationMultiplier is 1) { base.Update(); return; @@ -45,7 +44,7 @@ public override void Update() if (!NetworkServer.active) return; - if (_valueDirty) + if (ValueDirty) { new SyncedStatMessages.StatMessage() { @@ -53,7 +52,7 @@ public override void Update() SyncedValue = CurValue, }.SendToHubsConditionally(CanReceive); _lastSent = CurValue; - _valueDirty = false; + ValueDirty = false; } if (ShieldRegeneration == 0) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index 2008936bea..bd858b3447 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -155,6 +155,9 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage case DamageType.A7: GenericFirearm(player, attacker, damage, damageType, ItemType.GunA7); break; + case DamageType.Scp127: + GenericFirearm(player, attacker, damage, damageType, ItemType.GunSCP127); + break; case DamageType.ParticleDisruptor: Base = new DisruptorDamageHandler(new (Item.Create(ItemType.ParticleDisruptor, attacker).Base as InventorySystem.Items.Firearms.Firearm, InventorySystem.Items.Firearms.Modules.DisruptorActionModule.FiringState.FiringSingle), Vector3.up, damage); break; diff --git a/EXILED/Exiled.API/Features/Doors/BasicDoor.cs b/EXILED/Exiled.API/Features/Doors/BasicDoor.cs index 39316fdd37..5a53caa738 100644 --- a/EXILED/Exiled.API/Features/Doors/BasicDoor.cs +++ b/EXILED/Exiled.API/Features/Doors/BasicDoor.cs @@ -9,6 +9,8 @@ namespace Exiled.API.Features.Doors { using System.Collections.Generic; + using Exiled.API.Enums; + using PlayerRoles; using UnityEngine; using Basegame = Interactables.Interobjects.BasicDoor; @@ -35,9 +37,9 @@ public BasicDoor(Basegame door, List room) public new Basegame Base { get; } /// - /// Gets the list with all SCP-106's colliders. + /// Gets the list with all Ignored's colliders for or . /// - public IEnumerable Scp106Colliders => Base.Scp106Colliders; + public IEnumerable Scp106Colliders => Base.IgnoredColliders; /// /// Gets or sets the total cooldown before door can be triggered again. diff --git a/EXILED/Exiled.API/Features/Doors/CheckpointDoor.cs b/EXILED/Exiled.API/Features/Doors/CheckpointDoor.cs index 837bd5ff72..0fa6ea4139 100644 --- a/EXILED/Exiled.API/Features/Doors/CheckpointDoor.cs +++ b/EXILED/Exiled.API/Features/Doors/CheckpointDoor.cs @@ -42,10 +42,10 @@ internal CheckpointDoor(Interactables.Interobjects.CheckpointDoor door, List /// Gets or sets the current checkpoint stage. /// - public Interactables.Interobjects.CheckpointDoor.CheckpointSequenceStage CurrentStage + public Interactables.Interobjects.CheckpointDoor.SequenceState CurrentStage { - get => Base._currentSequence; - set => Base._currentSequence = value; + get => Base.CurSequence; + set => Base.CurSequence = value; } /// @@ -53,8 +53,8 @@ public Interactables.Interobjects.CheckpointDoor.CheckpointSequenceStage Current /// public float MainTimer { - get => Base._mainTimer; - set => Base._mainTimer = value; + get => Base.SequenceCtrl.RemainingTime; + set => Base.SequenceCtrl.RemainingTime = value; } /// @@ -62,8 +62,8 @@ public float MainTimer /// public float WaitTime { - get => Base._waitTime; - set => Base._waitTime = value; + get => Base.SequenceCtrl.OpenLoopTime; + set => Base.SequenceCtrl.OpenLoopTime = value; } /// @@ -71,8 +71,8 @@ public float WaitTime /// public float WarningTime { - get => Base._warningTime; - set => Base._warningTime = value; + get => Base.SequenceCtrl.WarningTime; + set => Base.SequenceCtrl.WarningTime = value; } /// diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index fb947039dc..c2cea9a761 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -22,11 +22,10 @@ namespace Exiled.API.Features.Doors using UnityEngine; using BaseBreakableDoor = Interactables.Interobjects.BreakableDoor; - using BaseKeycardPermissions = Interactables.Interobjects.DoorUtils.KeycardPermissions; using Breakable = BreakableDoor; using Checkpoint = CheckpointDoor; using Elevator = ElevatorDoor; - using KeycardPermissions = Enums.KeycardPermissions; + using KeycardPermissions = Exiled.API.Enums.KeycardPermissions; /// /// A wrapper class for . @@ -55,10 +54,6 @@ internal Door(DoorVariant door, List rooms) } Type = GetDoorType(); -#if DEBUG - if (Type is DoorType.UnknownDoor or DoorType.UnknownGate or DoorType.UnknownElevator) - Log.Error($"[DOORTYPE UNKNOWN] {this} BASE = {Base}"); -#endif } /// @@ -181,8 +176,8 @@ public bool IsOpen /// public KeycardPermissions KeycardPermissions { - get => (KeycardPermissions)RequiredPermissions.RequiredPermissions; - set => RequiredPermissions.RequiredPermissions = (BaseKeycardPermissions)value; + get => (KeycardPermissions)RequiredPermissions; + set => RequiredPermissions = (DoorPermissionFlags)value; } /// @@ -244,10 +239,10 @@ public DoorLockType DoorLockType /// /// Gets or sets the required permissions to open the door. /// - public DoorPermissions RequiredPermissions + public DoorPermissionFlags RequiredPermissions { - get => Base.RequiredPermissions; - set => Base.RequiredPermissions = value; + get => Base.RequiredPermissions.RequiredPermissions; + set => Base.RequiredPermissions.RequiredPermissions = value; } /// @@ -357,7 +352,7 @@ public static T Get(string name) /// /// The base-game . /// The with the given name or if not found. - public static Door Get(GameObject gameObject) => gameObject is null ? null : Get(gameObject.GetComponentInChildren()); + public static Door Get(GameObject gameObject) => gameObject is null ? null : Get(gameObject.GetComponentInParent()); /// /// Returns the closest to the given . @@ -474,11 +469,11 @@ public void PlaySound(DoorBeepType beep) { switch (Base) { - case Interactables.Interobjects.BasicDoor basic: - basic.RpcPlayBeepSound(beep is not DoorBeepType.InteractionAllowed); + case Interactables.Interobjects.BasicDoor basic when beep is not DoorBeepType.InteractionAllowed: + basic.RpcPlayBeepSound(); break; - case Interactables.Interobjects.CheckpointDoor chkPt: - chkPt.RpcPlayBeepSound((byte)Mathf.Min((int)beep, 3)); + case Interactables.Interobjects.CheckpointDoor chkPt when beep is not DoorBeepType.InteractionAllowed: + chkPt.RpcPlayDeniedBeep(); break; } } @@ -553,7 +548,7 @@ public void Lock(DoorLockType lockType) /// Returns the Door in a human-readable format. /// /// A string containing Door-related data. - public override string ToString() => $"{Type} ({Zone}) [{Room}] *{DoorLockType}* ={RequiredPermissions?.RequiredPermissions}="; + public override string ToString() => $"{Type} ({Zone}) [{Room}] *{DoorLockType}* ={KeycardPermissions}="; /// /// Creates the door object associated with a specific . @@ -613,6 +608,7 @@ private DoorType GetDoorType() ElevatorGroup.Scp049 => DoorType.ElevatorScp049, ElevatorGroup.GateB => DoorType.ElevatorGateB, ElevatorGroup.GateA => DoorType.ElevatorGateA, + ElevatorGroup.ServerRoom => DoorType.ElevatorServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => DoorType.ElevatorLczA, ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => DoorType.ElevatorLczB, ElevatorGroup.Nuke01 or ElevatorGroup.Nuke02 => DoorType.ElevatorNuke, @@ -654,8 +650,8 @@ private DoorType GetDoorType() "173_CONNECTOR" => DoorType.Scp173Connector, "LCZ_WC" => DoorType.LczWc, "HID_CHAMBER" => DoorType.HIDChamber, - "HID_UPPER" => DoorType.HIDUpper, - "HID_LOWER" => DoorType.HIDLower, + "HID_LAB" => DoorType.HIDLab, + "HCZ_127_LAB" => DoorType.Hcz127Lab, "173_ARMORY" => DoorType.Scp173Armory, "173_GATE" => DoorType.Scp173Gate, "GR18" => DoorType.GR18Gate, diff --git a/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs b/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs index 797378312a..0145e53f84 100644 --- a/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs +++ b/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs @@ -30,7 +30,7 @@ internal ElevatorDoor(Interactables.Interobjects.ElevatorDoor door, List r Base = door; Lift = Lift.Get(x => x.Group == Group).FirstOrDefault(); - Panel = Object.FindObjectsOfType().FirstOrDefault(x => x._door == door); + Panel = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None).FirstOrDefault(x => x._door == door); } /// @@ -56,6 +56,7 @@ internal ElevatorDoor(Interactables.Interobjects.ElevatorDoor door, List r 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/Generator.cs b/EXILED/Exiled.API/Features/Generator.cs index 7b8a6b6baf..b19369208d 100644 --- a/EXILED/Exiled.API/Features/Generator.cs +++ b/EXILED/Exiled.API/Features/Generator.cs @@ -13,7 +13,7 @@ namespace Exiled.API.Features using Enums; using Exiled.API.Interfaces; - + using Interactables.Interobjects.DoorUtils; using MapGeneration.Distributors; using UnityEngine; @@ -216,7 +216,7 @@ public Player LastActivator public KeycardPermissions KeycardPermissions { get => (KeycardPermissions)Base._requiredPermission; - set => Base._requiredPermission = (Interactables.Interobjects.DoorUtils.KeycardPermissions)value; + set => Base._requiredPermission = (DoorPermissionFlags)value; } /// @@ -281,7 +281,13 @@ public static bool TryGet(Func predicate, out IEnumerable /// Denies the unlock. /// - public void DenyUnlock() => Base.RpcDenied(); + public void DenyUnlock() => Base.RpcDenied(DoorPermissionFlags.None); + + /// + /// Denies the unlock. + /// + /// . + public void DenyUnlock(KeycardPermissions doorPermission) => Base.RpcDenied((Interactables.Interobjects.DoorUtils.DoorPermissionFlags)doorPermission); /// /// Denies the unlock and resets the interaction cooldown. @@ -299,7 +305,7 @@ public void DenyUnlockAndResetCooldown() /// A value indicating whether the flag is enabled. public void SetPermissionFlag(KeycardPermissions flag, bool isEnabled) { - Interactables.Interobjects.DoorUtils.KeycardPermissions permission = (Interactables.Interobjects.DoorUtils.KeycardPermissions)flag; + DoorPermissionFlags permission = (DoorPermissionFlags)flag; if (isEnabled) Base._requiredPermission |= permission; diff --git a/EXILED/Exiled.API/Features/Hazards/TemporaryHazard.cs b/EXILED/Exiled.API/Features/Hazards/TemporaryHazard.cs index 0e701a8093..c9dfecb650 100644 --- a/EXILED/Exiled.API/Features/Hazards/TemporaryHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/TemporaryHazard.cs @@ -56,8 +56,8 @@ public bool IsDestroyed /// public float Duration { - get => Base._elapsed; - set => Base._elapsed = value; + get => Base.Elapsed; + set => Base.Elapsed = value; } /// diff --git a/EXILED/Exiled.API/Features/Items/Armor.cs b/EXILED/Exiled.API/Features/Items/Armor.cs index 238de555ec..344b7a0118 100644 --- a/EXILED/Exiled.API/Features/Items/Armor.cs +++ b/EXILED/Exiled.API/Features/Items/Armor.cs @@ -65,21 +65,22 @@ internal Armor(ItemType type) public bool IsWorn => Base.IsWorn; /// - /// Gets or sets the Weight of the armor. + /// Gets or sets a value indicating whether excess ammo should be removed when the armor is dropped. /// - public new float Weight + [Obsolete("Not functional anymore", true)] + public bool RemoveExcessOnDrop { - get => Base.Weight; - set => Base._weight = value; + get => false; + set => _ = value; } /// - /// Gets or sets a value indicating whether excess ammo should be removed when the armor is dropped. + /// Gets or sets the Weight of the armor. /// - public bool RemoveExcessOnDrop + public new float Weight { - get => !Base.DontRemoveExcessOnDrop; - set => Base.DontRemoveExcessOnDrop = !value; + get => Base.Weight; + set => Base._weight = value; } /// diff --git a/EXILED/Exiled.API/Features/Items/Keycard.cs b/EXILED/Exiled.API/Features/Items/Keycard.cs index 0bd390018b..4e68dd60a5 100644 --- a/EXILED/Exiled.API/Features/Items/Keycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycard.cs @@ -7,14 +7,12 @@ namespace Exiled.API.Features.Items { + using System; + using Exiled.API.Enums; - using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; - using InventorySystem.Items.Keycards; - using KeycardPickup = Pickups.KeycardPickup; - /// /// A wrapper class for . /// @@ -49,33 +47,30 @@ internal Keycard(ItemType type) /// public KeycardPermissions Permissions { - get => (KeycardPermissions)Base.Permissions; - set => Base.Permissions = (Interactables.Interobjects.DoorUtils.KeycardPermissions)value; - } + get + { + foreach (DetailBase detail in Base.Details) + { + switch (detail) + { + case PredefinedPermsDetail predefinedPermsDetail: + return (KeycardPermissions)predefinedPermsDetail.Levels.Permissions; + case CustomPermsDetail customPermsDetail: + return (KeycardPermissions)customPermsDetail.GetPermissions(null); + } + } - /// - /// Clones current object. - /// - /// New object. - public override Item Clone() => new Keycard(Type) - { - Permissions = Permissions, - }; + return KeycardPermissions.None; + } + + [Obsolete("Not functional anymore", true)] + set => _ = value; + } /// /// Returns the Keycard in a human readable format. /// /// A string containing Keycard-related data. public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; - - /// - internal override void ReadPickupInfoBefore(Pickup pickup) - { - base.ReadPickupInfoBefore(pickup); - if (pickup is KeycardPickup keycardPickup) - { - Permissions = keycardPickup.Permissions; - } - } } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Scp1344.cs b/EXILED/Exiled.API/Features/Items/Scp1344.cs index 460aeb8c06..7f96da8903 100644 --- a/EXILED/Exiled.API/Features/Items/Scp1344.cs +++ b/EXILED/Exiled.API/Features/Items/Scp1344.cs @@ -11,7 +11,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp1344; - using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers; + using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables; /// /// A wrapper class for . diff --git a/EXILED/Exiled.API/Features/Lockers/Chamber.cs b/EXILED/Exiled.API/Features/Lockers/Chamber.cs index af4fba905d..e98281ed5c 100644 --- a/EXILED/Exiled.API/Features/Lockers/Chamber.cs +++ b/EXILED/Exiled.API/Features/Lockers/Chamber.cs @@ -78,13 +78,13 @@ public Chamber(LockerChamber chamber, Locker locker) /// public IEnumerable ToBeSpawned { - get => Base._toBeSpawned.Select(Pickup.Get); + get => Base.ToBeSpawned.Select(Pickup.Get); set { - Base._toBeSpawned.Clear(); + Base.ToBeSpawned.Clear(); foreach (Pickup pickup in value) - Base._toBeSpawned.Add(pickup.Base); + Base.ToBeSpawned.Add(pickup.Base); } } @@ -115,7 +115,7 @@ public IEnumerable AcceptableTypes public KeycardPermissions RequiredPermissions { get => (KeycardPermissions)Base.RequiredPermissions; - set => Base.RequiredPermissions = (Interactables.Interobjects.DoorUtils.KeycardPermissions)value; + set => Base.RequiredPermissions = (Interactables.Interobjects.DoorUtils.DoorPermissionFlags)value; } /// @@ -138,8 +138,8 @@ public bool UseMultipleSpawnpoints /// public Transform Spawnpoint { - get => Base._spawnpoint; - set => Base._spawnpoint = value; + get => Base.Spawnpoint; + set => Base.Spawnpoint = value; } /// @@ -147,8 +147,8 @@ public Transform Spawnpoint /// public bool InitiallySpawn { - get => Base._spawnOnFirstChamberOpening; - set => Base._spawnOnFirstChamberOpening = value; + get => Base.SpawnOnFirstChamberOpening; + set => Base.SpawnOnFirstChamberOpening = value; } /// @@ -156,8 +156,8 @@ public bool InitiallySpawn /// public float Cooldown { - get => Base._targetCooldown; - set => Base._targetCooldown = value; + get => Base.TargetCooldown; + set => Base.TargetCooldown = value; } /// @@ -235,7 +235,7 @@ public void AddItem(Pickup item) Base.Content.Add(item.Base); item.Spawn(); - if (Base._wasEverOpened) + if (Base.WasEverOpened) item.IsLocked = false; } @@ -270,7 +270,7 @@ public void AddItemToSpawn(ItemType itemType, int quantity = 1, bool spawnIfIsOp continue; } - Base._toBeSpawned.Add(pickup.Base); + Base.ToBeSpawned.Add(pickup.Base); } } @@ -296,6 +296,6 @@ public Vector3 GetRandomSpawnPoint() /// /// . /// . - internal static Chamber Get(LockerChamber chamber) => Chambers.TryGetValue(chamber, out Chamber chmb) ? chmb : new(chamber, Locker.Get(x => x.Chambers.Any(x => x.Base == chamber)).FirstOrDefault()); + internal static Chamber Get(LockerChamber chamber) => chamber == null ? null : Chambers.TryGetValue(chamber, out Chamber chmb) ? chmb : new(chamber, Locker.Get(x => x.Chambers.Any(x => x.Base == chamber)).FirstOrDefault()); } } diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 95888ddd8b..add2a5123b 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -107,7 +107,7 @@ public static bool IsDecontaminationEnabled /// /// Gets the . /// - public static SqueakSpawner SqueakSpawner => squeakSpawner ??= Object.FindObjectOfType(); + public static SqueakSpawner SqueakSpawner => squeakSpawner ??= Object.FindFirstObjectByType(); /// /// Sends a staff message to all players online with permission. diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 698b74ad80..7791218042 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -11,32 +11,27 @@ namespace Exiled.API.Features using System; using System.Collections.Generic; using System.Linq; - using System.Reflection; - using CentralAuth; using CommandSystem; using CommandSystem.Commands.RemoteAdmin.Dummies; using Exiled.API.Enums; - using Exiled.API.Features.Components; using Exiled.API.Features.CustomStats; using Exiled.API.Features.Roles; using Footprinting; - using GameCore; using MEC; using Mirror; + using NetworkManagerUtils.Dummies; using PlayerRoles; using PlayerStatsSystem; using UnityEngine; - using Object = UnityEngine.Object; - /// /// Wrapper class for handling NPC players. /// public class Npc : Player { /// - /// The time it takes for the NPC to receive its , and . + /// The time it takes for the NPC to receive its and . /// public const float SpawnSetRoleDelay = 0.5f; @@ -263,7 +258,7 @@ public static Npc Spawn(string name, RoleTypeId role, Vector3 position) { npc.Role.Set(role, SpawnReason.ForceClass); npc.Position = position; - npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = npc.CustomHealthStat = new CustomHealthStat { Hub = npc.ReferenceHub }; + npc.CustomHealthStat = (HealthStat)npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)]; npc.Health = npc.MaxHealth; // otherwise the npc will spawn with 0 health npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = npc.CustomHumeShieldStat = new CustomHumeShieldStat { Hub = npc.ReferenceHub }; }); @@ -287,7 +282,7 @@ public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ign Timing.CallDelayed(SpawnSetRoleDelay, () => { npc.Role.Set(role, SpawnReason.ForceClass, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory); - npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = npc.CustomHealthStat = new CustomHealthStat { Hub = npc.ReferenceHub }; + npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = npc.CustomHealthStat = new HealthStat { Hub = npc.ReferenceHub }; npc.Health = npc.MaxHealth; // otherwise the npc will spawn with 0 health npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = npc.CustomHumeShieldStat = new CustomHumeShieldStat { Hub = npc.ReferenceHub }; diff --git a/EXILED/Exiled.API/Features/Paths.cs b/EXILED/Exiled.API/Features/Paths.cs index 153d8e0bf6..8d99b1a996 100644 --- a/EXILED/Exiled.API/Features/Paths.cs +++ b/EXILED/Exiled.API/Features/Paths.cs @@ -11,6 +11,8 @@ namespace Exiled.API.Features using System.IO; using System.Linq; + using LabApi.Loader; + /// /// A set of useful paths. /// @@ -106,7 +108,7 @@ public static void Reload(string rootDirectory = null) Dependencies = Path.Combine(Plugins, "dependencies"); Configs = Path.Combine(Exiled, "Configs"); IndividualConfigs = Path.Combine(Configs, "Plugins"); - LoaderConfig = PluginAPI.Loader.AssemblyLoader.InstalledPlugins.FirstOrDefault(x => x.PluginName == "Exiled Loader")?.MainConfigPath; + LoaderConfig = LabApi.Loader.PluginLoader.EnabledPlugins.FirstOrDefault(x => x.Name == "Exiled Loader")?.GetConfigPath("Exiled Loader"); Config = Path.Combine(Configs, $"{Server.Port}-config.yml"); BackupConfig = Path.Combine(Configs, $"{Server.Port}-config.yml.old"); IndividualTranslations = Path.Combine(Configs, "Translations"); diff --git a/EXILED/Exiled.API/Features/Pickups/ExplosiveGrenadePickup.cs b/EXILED/Exiled.API/Features/Pickups/ExplosiveGrenadePickup.cs index 061135573d..62484bcceb 100644 --- a/EXILED/Exiled.API/Features/Pickups/ExplosiveGrenadePickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/ExplosiveGrenadePickup.cs @@ -96,8 +96,8 @@ protected override void InitializeProperties(ItemBase itemBase) base.InitializeProperties(itemBase); if (itemBase is ThrowableItem throwable && throwable.Projectile is ExplosionGrenade explosiveGrenade) { - MaxRadius = explosiveGrenade._maxRadius; - ScpDamageMultiplier = explosiveGrenade._scpDamageMultiplier; + MaxRadius = explosiveGrenade.MaxRadius; + ScpDamageMultiplier = explosiveGrenade.ScpDamageMultiplier; BurnDuration = explosiveGrenade._burnedDuration; DeafenDuration = explosiveGrenade._deafenedDuration; ConcussDuration = explosiveGrenade._concussedDuration; diff --git a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs index 49115119b9..e7e29ff13e 100644 --- a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs @@ -42,9 +42,9 @@ internal KeycardPickup(ItemType type) } /// - /// Gets or sets the of the keycard. + /// Gets the of the keycard. /// - public KeycardPermissions Permissions { get; set; } + public KeycardPermissions Permissions { get; private set; } /// /// Gets the that this class is encapsulating. @@ -67,7 +67,18 @@ protected override void InitializeProperties(ItemBase itemBase) base.InitializeProperties(itemBase); if (itemBase is KeycardItem keycardItem) { - Permissions = (KeycardPermissions)keycardItem.Permissions; + foreach (DetailBase detail in keycardItem.Details) + { + switch (detail) + { + case PredefinedPermsDetail predefinedPermsDetail: + Permissions = (KeycardPermissions)predefinedPermsDetail.Levels.Permissions; + return; + case CustomPermsDetail customPermsDetail: + Permissions = (KeycardPermissions)customPermsDetail.GetPermissions(null); + return; + } + } } } } diff --git a/EXILED/Exiled.API/Features/Pickups/Projectiles/ExplosionGrenadeProjectile.cs b/EXILED/Exiled.API/Features/Pickups/Projectiles/ExplosionGrenadeProjectile.cs index 70a40ce880..07cdbebefb 100644 --- a/EXILED/Exiled.API/Features/Pickups/Projectiles/ExplosionGrenadeProjectile.cs +++ b/EXILED/Exiled.API/Features/Pickups/Projectiles/ExplosionGrenadeProjectile.cs @@ -49,8 +49,8 @@ internal ExplosionGrenadeProjectile(ItemType type) /// public float MaxRadius { - get => Base._maxRadius; - set => Base._maxRadius = value; + get => Base.MaxRadius; + set => Base.MaxRadius = value; } /// @@ -94,8 +94,8 @@ public float ConcussDuration /// public float ScpDamageMultiplier { - get => Base._scpDamageMultiplier; - set => Base._scpDamageMultiplier = value; + get => Base.ScpDamageMultiplier; + set => Base.ScpDamageMultiplier = value; } /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index d13a2265fe..4dfcf09409 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -53,7 +53,6 @@ namespace Exiled.API.Features using PlayerRoles.Spectating; using PlayerRoles.Voice; using PlayerStatsSystem; - using PluginAPI.Core; using RelativePositioning; using RemoteAdmin; using RoundRestarting; @@ -95,6 +94,7 @@ public class Player : TypeCastObject, IEntity, IWorldSpace private readonly HashSet componentsInChildren = new(); private ReferenceHub referenceHub; + private Role role; /// @@ -180,7 +180,7 @@ private set Inventory = value.inventory; CameraTransform = value.PlayerCameraReference; - value.playerStats._dictionarizedTypes[typeof(HealthStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = CustomHealthStat = new CustomHealthStat { Hub = value }; + CustomHealthStat = (HealthStat)value.playerStats._dictionarizedTypes[typeof(HealthStat)]; value.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = CustomHumeShieldStat = new CustomHumeShieldStat { Hub = value }; } } @@ -351,37 +351,9 @@ public string CustomInfo get => ReferenceHub.nicknameSync.Network_customPlayerInfoString; set { - // NW Client check. - if (value.Contains('<')) + if (!NicknameSync.ValidateCustomInfo(value, out string rejectionText)) { - foreach (string token in value.Split('<')) - { - if (token.StartsWith("/", StringComparison.Ordinal) || - token.StartsWith("b>", StringComparison.Ordinal) || - token.StartsWith("i>", StringComparison.Ordinal) || - token.StartsWith("size=", StringComparison.Ordinal) || - token.Length is 0) - continue; - - if (token.StartsWith("color=", StringComparison.Ordinal)) - { - if (token.Length < 14 || token[13] != '>') - Log.Error($"Custom info of player {Nickname} has been REJECTED. \nreason: (Bad text reject) \ntoken: {token} \nInfo: {value}"); - else if (!Misc.AllowedColors.ContainsValue(token.Substring(6, 7))) - Log.Error($"Custom info of player {Nickname} has been REJECTED. \nreason: (Bad color reject) \ntoken: {token} \nInfo: {value}"); - } - else if (token.StartsWith("#", StringComparison.Ordinal)) - { - if (token.Length < 8 || token[7] != '>') - Log.Error($"Custom info of player {Nickname} has been REJECTED. \nreason: (Bad text reject) \ntoken: {token} \nInfo: {value}"); - else if (!Misc.AllowedColors.ContainsValue(token.Substring(0, 7))) - Log.Error($"Custom info of player {Nickname} has been REJECTED. \nreason: (Bad color reject) \ntoken: {token} \nInfo: {value}"); - } - else - { - Log.Error($"Custom info of player {Nickname} has been REJECTED. \nreason: (Bad color reject) \ntoken: {token} \nInfo: {value}"); - } - } + Log.Error($"Could not set CustomInfo for {Nickname}. Reason: {rejectionText}"); } InfoArea = string.IsNullOrEmpty(value) ? InfoArea & ~PlayerInfoArea.CustomInfo : InfoArea |= PlayerInfoArea.CustomInfo; @@ -424,7 +396,7 @@ public float InfoViewRange /// /// /// - public bool HasReservedSlot => ReservedSlot.HasReservedSlot(UserId, out _); + public bool HasReservedSlot => ReservedSlot.HasReservedSlot(UserId); /// /// Gets a value indicating whether the player is in whitelist. @@ -511,7 +483,7 @@ public Player Cuffer public virtual Vector3 Position { get => Transform.position; - set => ReferenceHub.TryOverridePosition(value, Vector3.zero); + set => ReferenceHub.TryOverridePosition(value); } /// @@ -531,7 +503,7 @@ public RelativePosition RelativePosition public Quaternion Rotation { get => Transform.rotation; - set => ReferenceHub.TryOverridePosition(Position, value.eulerAngles); + set => ReferenceHub.TryOverrideRotation(value.eulerAngles); } /// @@ -885,7 +857,7 @@ public float Health public float MaxHealth { get => CustomHealthStat.MaxValue; - set => CustomHealthStat.CustomMaxValue = value; + set => CustomHealthStat.MaxValue = value; } /// @@ -941,7 +913,7 @@ public float HumeShield public float MaxHumeShield { get => CustomHumeShieldStat.MaxValue; - set => CustomHumeShieldStat.CustomMaxValue = value; + set => CustomHumeShieldStat.MaxValue = value; } /// @@ -1019,8 +991,8 @@ public float Stamina /// public string GroupName { - get => ServerStatic.PermissionsHandler._members.TryGetValue(UserId, out string groupName) ? groupName : null; - set => ServerStatic.PermissionsHandler._members[UserId] = value; + get => ServerStatic.PermissionsHandler.Members.TryGetValue(UserId, out string groupName) ? groupName : null; + set => ServerStatic.PermissionsHandler.Members[UserId] = value; } /// @@ -1189,14 +1161,21 @@ public bool IsSpawnProtected /// /// Gets or sets a . /// - protected CustomHealthStat CustomHealthStat { get; set; } + protected HealthStat CustomHealthStat { get; set; } + + /// + /// Converts LabApi player to EXILED player. + /// + /// The LabApi player. + /// EXILED player. + public static implicit operator Player(LabApi.Features.Wrappers.Player player) => Get(player); /// - /// Converts NwPluginAPI player to EXILED player. + /// Converts LabApi player to EXILED player. /// - /// The NwPluginAPI player. + /// The LabApi player. /// EXILED player. - public static implicit operator Player(PluginAPI.Core.Player player) => Get(player); + public static implicit operator LabApi.Features.Wrappers.Player(Player player) => LabApi.Features.Wrappers.Player.Get(player.ReferenceHub); /// /// Gets a filtered by side. Can be empty. @@ -1387,11 +1366,11 @@ public static Player Get(string args) } /// - /// Gets the from NwPluginAPI class. + /// Gets the from LabApi class. /// - /// The class. + /// The class. /// A or if not found. - public static Player Get(PluginAPI.Core.Player apiPlayer) => Get(apiPlayer.ReferenceHub); + public static Player Get(LabApi.Features.Wrappers.Player apiPlayer) => Get(apiPlayer.ReferenceHub); /// /// Try-get a player given a . @@ -1474,12 +1453,12 @@ public static Player Get(string args) public static bool TryGet(string args, out Player player) => (player = Get(args)) is not null; /// - /// Try-get the from NwPluginAPI class. + /// Try-get the from LabApi class. /// - /// The class. + /// The class. /// The player found or if not found. /// A boolean indicating whether a player was found. - public static bool TryGet(PluginAPI.Core.Player apiPlayer, out Player player) => (player = Get(apiPlayer)) is not null; + public static bool TryGet(LabApi.Features.Wrappers.Player apiPlayer, out Player player) => (player = Get(apiPlayer)) is not null; /// /// Try-get player by . @@ -1518,10 +1497,10 @@ public static bool AddReservedSlot(string userId, bool isPermanent) { if (isPermanent) { - if (ReservedSlots.HasReservedSlot(userId)) + if (LabApi.Features.Wrappers.ReservedSlots.HasReservedSlot(userId)) return false; - ReservedSlots.Add(userId); + LabApi.Features.Wrappers.ReservedSlots.Add(userId); return true; } @@ -1542,7 +1521,7 @@ public static bool AddToWhitelist(string userId, bool isPermanent) if (WhiteList.IsOnWhitelist(userId)) return false; - Whitelist.Add(userId); + LabApi.Features.Wrappers.Whitelist.Add(userId); return true; } @@ -1834,7 +1813,7 @@ public bool TryGetItem(ushort serial, out Item item) /// The group to be set. public void SetRank(string name, UserGroup group) { - if (ServerStatic.GetPermissionsHandler()._groups.TryGetValue(name, out UserGroup userGroup)) + if (ServerStatic.PermissionsHandler.Groups.TryGetValue(name, out UserGroup userGroup)) { userGroup.BadgeColor = group.BadgeColor; userGroup.BadgeText = name; @@ -1845,15 +1824,15 @@ public void SetRank(string name, UserGroup group) } else { - ServerStatic.GetPermissionsHandler()._groups.Add(name, group); + ServerStatic.PermissionsHandler.Groups.Add(name, group); ReferenceHub.serverRoles.SetGroup(group, false, false); } - if (ServerStatic.GetPermissionsHandler()._members.ContainsKey(UserId)) - ServerStatic.GetPermissionsHandler()._members[UserId] = name; + if (ServerStatic.PermissionsHandler.Members.ContainsKey(UserId)) + ServerStatic.PermissionsHandler.Members[UserId] = name; else - ServerStatic.GetPermissionsHandler()._members.Add(UserId, name); + ServerStatic.PermissionsHandler.Members.Add(UserId, name); } /// @@ -2983,9 +2962,6 @@ public void ClearInventory(bool destroy = true) /// public void ClearItems(bool destroy = true) { - if (CurrentArmor is Armor armor) - armor.RemoveExcessOnDrop = false; - while (Items.Count > 0) RemoveItem(Items.ElementAt(0), destroy); } @@ -3609,7 +3585,7 @@ public void Teleport(object obj, Vector3 offset) Teleport(locker.transform.position + Vector3.up + offset); break; case LockerChamber chamber: - Teleport(chamber._spawnpoint.position + Vector3.up + offset); + Teleport(chamber.Spawnpoint.position + Vector3.up + offset); break; case ElevatorChamber elevator: Teleport(elevator.transform.position + Vector3.up + offset); diff --git a/EXILED/Exiled.API/Features/Ragdoll.cs b/EXILED/Exiled.API/Features/Ragdoll.cs index 8cde5d8cb2..011c092248 100644 --- a/EXILED/Exiled.API/Features/Ragdoll.cs +++ b/EXILED/Exiled.API/Features/Ragdoll.cs @@ -101,7 +101,7 @@ public RagdollData NetworkInfo public DamageHandlerBase DamageHandler { get => NetworkInfo.Handler; - set => NetworkInfo = new(NetworkInfo.OwnerHub, value, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, value, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -145,7 +145,16 @@ public bool CanBeCleanedUp public string Nickname { get => NetworkInfo.Nickname; - set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, value, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, value, NetworkInfo.CreationTime); + } + + /// + /// Gets or sets the ragdoll's Scale with RagdollData. + /// + public Vector3 Scale + { + get => NetworkInfo.Scale; + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, value, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -159,7 +168,7 @@ public string Nickname public Player Owner { get => Player.Get(NetworkInfo.OwnerHub); - set => NetworkInfo = new(value.ReferenceHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(value.ReferenceHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -171,7 +180,7 @@ public DateTime CreationTime set { float creationTime = (float)(NetworkTime.time - (DateTime.Now - value).TotalSeconds); - NetworkInfo = new RagdollData(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Nickname, creationTime); + NetworkInfo = new RagdollData(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, creationTime); } } @@ -181,7 +190,7 @@ public DateTime CreationTime public RoleTypeId Role { get => NetworkInfo.RoleType; - set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, value, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, value, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -248,9 +257,9 @@ public Quaternion Rotation } /// - /// Gets or sets the ragdoll's scale. + /// Gets or sets the ragdoll's as Gameobjectscale. /// - public Vector3 Scale + public Vector3 RagdollScale { get => Base.transform.localScale; set diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index ecc946c9b7..d1c0a12591 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -27,7 +27,6 @@ namespace Exiled.API.Features.Roles /// public abstract class FpcRole : Role, IVoiceRole { - private static FieldInfo enableFallDamageField; private bool isUsingStamina = true; /// @@ -68,17 +67,31 @@ public RelativePosition ClientRelativePosition set => FirstPersonController.FpcModule.Motor.ReceivedPosition = value; } + /// + /// Gets or sets the player's gravity. + /// + public Vector3 Gravity + { + get => FirstPersonController.FpcModule.Motor.GravityController.Gravity; + set => FirstPersonController.FpcModule.Motor.GravityController.Gravity = value; + } + /// /// Gets or sets a value indicating whether if the player should get damage. /// public bool IsFallDamageEnable { - get => FirstPersonController.FpcModule.Motor._enableFallDamage; - set - { - enableFallDamageField ??= AccessTools.Field(typeof(FpcMotor), nameof(FpcMotor._enableFallDamage)); - enableFallDamageField.SetValue(FirstPersonController.FpcModule.Motor, value); - } + get => FirstPersonController.FpcModule.Motor._fallDamageSettings.Enabled; + set => FirstPersonController.FpcModule.Motor._fallDamageSettings.Enabled = value; + } + + /// + /// Gets or sets the multiplier of damage. + /// + public float FallDamageMultiplier + { + get => FirstPersonController.FpcModule.Motor._fallDamageSettings.Multiplier; + set => FirstPersonController.FpcModule.Motor._fallDamageSettings.Multiplier = value; } /// diff --git a/EXILED/Exiled.API/Features/Roles/HumanRole.cs b/EXILED/Exiled.API/Features/Roles/HumanRole.cs index 8a238a3e7d..767a010d45 100644 --- a/EXILED/Exiled.API/Features/Roles/HumanRole.cs +++ b/EXILED/Exiled.API/Features/Roles/HumanRole.cs @@ -8,7 +8,7 @@ namespace Exiled.API.Features.Roles { using PlayerRoles; - + using PlayerRoles.PlayableScps.HumeShield; using Respawning; using Respawning.NamingRules; @@ -17,7 +17,7 @@ namespace Exiled.API.Features.Roles /// /// Defines a role that represents a human class. /// - public class HumanRole : FpcRole + public class HumanRole : FpcRole, IHumeShieldRole { /// /// Initializes a new instance of the class. @@ -27,6 +27,7 @@ internal HumanRole(HumanGameRole baseRole) : base(baseRole) { Base = baseRole; + HumeShieldModule = baseRole.HumeShieldModule; } /// @@ -56,6 +57,9 @@ public byte UnitNameId /// public new HumanGameRole Base { get; } + /// + public HumeShieldModuleBase HumeShieldModule { get; } + /// /// Gets the armor efficacy based on a specific and the armor the is wearing. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs index 9c0ac35eb4..6a58fcd444 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs @@ -118,40 +118,6 @@ internal Scp049Role(Scp049GameRole baseRole) /// public IEnumerable DeadZombies => Scp049ResurrectAbility.DeadZombies.Select(x => Player.Get(x)); - // TODO: ReAdd Setter but before making an propper way to overwrite NW constant only when the propperty has been used -#pragma warning disable SA1623 // Property summary documentation should match accessors -#pragma warning disable SA1202 - /// - /// Gets or sets how mush time the Call Ability will be effective. - /// - internal double CallAbilityDuration { get; } = Scp049CallAbility.EffectDuration; - - /// - /// Gets or sets the Cooldown of the Call Ability. - /// - internal double CallAbilityBaseCooldown { get; } = Scp049CallAbility.BaseCooldown; - - /// - /// Gets or sets the Cooldown of the Sense Ability. - /// - internal double SenseAbilityBaseCooldown { get; } = Scp049SenseAbility.BaseCooldown; - - /// - /// Gets or sets the Cooldown of the Sense Ability when you lost your target. - /// - internal double SenseAbilityReducedCooldown { get; } = Scp049SenseAbility.ReducedCooldown; - - /// - /// Gets or sets the Cooldown of the Sense Ability when it's failed. - /// - internal double SenseAbilityDuration { get; } = Scp049SenseAbility.EffectDuration; - - /// - /// Gets or sets how mush time the Sense Ability will be effective. - /// - internal double SenseAbilityFailCooldown { get; } = Scp049SenseAbility.AttemptFailCooldown; -#pragma warning restore SA1623 // Property summary documentation should match accessors - /// /// Gets all the resurrected players. /// @@ -319,7 +285,7 @@ public void Sense(Player player) if (SenseAbility.Target is null) { - SenseAbility.Cooldown.Trigger(SenseAbilityFailCooldown); + SenseAbility.Cooldown.Trigger(Scp049SenseAbility.AttemptFailCooldown); SenseAbility.ServerSendRpc(true); return; } @@ -332,7 +298,7 @@ public void Sense(Player player) if (!VisionInformation.GetVisionInformation(SenseAbility.Owner, SenseAbility.Owner.PlayerCameraReference, humanRole.CameraPosition, radius, SenseAbility._distanceThreshold).IsLooking) return; - SenseAbility.Duration.Trigger(SenseAbilityDuration); + SenseAbility.Duration.Trigger(Scp049SenseAbility.EffectDuration); SenseAbility.HasTarget = true; SenseAbility.ServerSendRpc(true); } diff --git a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs index 2480ca3d88..c2b8cb70ce 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs @@ -593,7 +593,7 @@ public void ActivateTesla(bool consumeEnergy = true) Scp079Camera cam = CurrentCameraSync.CurrentCamera; RewardManager.MarkRoom(cam.Room); - if (!global::TeslaGate.AllGates.TryGetFirst(x => RoomIdUtils.IsTheSameRoom(cam.Position, x.transform.position), out global::TeslaGate teslaGate)) + if (!global::TeslaGate.AllGates.TryGetFirst(x => cam.Position.TryGetRoom(out RoomIdentifier camRoom) && x.transform.position.TryGetRoom(out RoomIdentifier teslaRoom) && camRoom == teslaRoom, out global::TeslaGate teslaGate)) return; if (consumeEnergy) diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index 9fde18957c..e5bcc9c0d5 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -246,7 +246,7 @@ public static Room Get(RoomIdentifier roomIdentifier) => roomIdentifier == null /// /// The to search for. /// The with the given or if not found. - public static Room Get(Vector3 position) => RoomIdUtils.RoomAtPositionRaycasts(position, false) is RoomIdentifier identifier ? Get(identifier) : null; + public static Room Get(Vector3 position) => position.TryGetRoom(out RoomIdentifier room) ? Get(room) : null; /// /// Gets a given the specified . @@ -421,7 +421,7 @@ internal void InternalCreate() Identifier = gameObject.GetComponent(); RoomIdentifierToRoom.Add(Identifier, this); - Zone = FindZone(gameObject); + Zone = Identifier.Zone.GetZone(); #if DEBUG if (Zone is ZoneType.Unspecified) Log.Error($"[ZONETYPE UNKNOWN] {this} Zone : {Identifier?.Zone}"); @@ -496,6 +496,8 @@ private static RoomType FindType(GameObject gameObject) "HCZ_Straight Variant" => RoomType.HczStraightVariant, "HCZ_ChkpA" => RoomType.HczElevatorA, "HCZ_ChkpB" => RoomType.HczElevatorB, + "HCZ_127" => RoomType.Hcz127, + "HCZ_ServerRoom" => RoomType.HczServerRoom, "EZ_GateA" => RoomType.EzGateA, "EZ_GateB" => RoomType.EzGateB, "EZ_ThreeWay" => RoomType.EzTCross, @@ -527,22 +529,5 @@ private static RoomType FindType(GameObject gameObject) _ => RoomType.Unknown, }; } - - private static ZoneType FindZone(GameObject gameObject) - { - Transform transform = gameObject.transform; - - if (gameObject.name == "PocketWorld") - return ZoneType.Pocket; - - return transform.parent?.name.RemoveBracketsOnEndOfName() switch - { - "HeavyRooms" => ZoneType.HeavyContainment, - "LightRooms" => ZoneType.LightContainment, - "EntranceRooms" => ZoneType.Entrance, - "HCZ_EZ_Checkpoint" => ZoneType.HeavyContainment | ZoneType.Entrance, - _ => transform.position.y > 900 ? ZoneType.Surface : ZoneType.Unspecified, - }; - } } } diff --git a/EXILED/Exiled.API/Features/Round.cs b/EXILED/Exiled.API/Features/Round.cs index b6ee2b35fc..a278e169dc 100644 --- a/EXILED/Exiled.API/Features/Round.cs +++ b/EXILED/Exiled.API/Features/Round.cs @@ -54,7 +54,7 @@ public static class Round /// /// Gets a value indicating whether the round is ended. /// - public static bool IsEnded => RoundSummary._singletonSet && RoundSummary.singleton._roundEnded; + public static bool IsEnded => RoundSummary._singletonSet && RoundSummary.singleton.IsRoundEnded; /// /// Gets a value indicating whether the round is lobby. diff --git a/EXILED/Exiled.API/Features/Scp914.cs b/EXILED/Exiled.API/Features/Scp914.cs index 57ee9d366c..7d9ea65818 100644 --- a/EXILED/Exiled.API/Features/Scp914.cs +++ b/EXILED/Exiled.API/Features/Scp914.cs @@ -40,8 +40,8 @@ public static Scp914KnobSetting KnobStatus /// public static Scp914Mode ConfigMode { - get => Scp914Controller._configMode.Value; - set => Scp914Controller._configMode.Value = value; + get => Scp914Controller.ConfigMode.Value; + set => Scp914Controller.ConfigMode.Value = value; } /// @@ -72,7 +72,7 @@ public static Scp914Mode ConfigMode /// /// Gets a value indicating whether SCP-914 is active and currently processing items. /// - public static bool IsWorking => Scp914Controller._isUpgrading; + public static bool IsWorking => Scp914Controller.IsUpgrading; /// /// Gets a value indicating all of the GameObjects currently present inside SCP-914's intake chamber. @@ -92,7 +92,7 @@ public static Scp914Mode ConfigMode /// /// Gets the list with which SCP-914 has. /// - public static IReadOnlyCollection Doors => Scp914Controller._doors.Select(Door.Get).ToList(); + public static IReadOnlyCollection Doors => Scp914Controller.Doors.Select(Door.Get).ToList(); /// /// Filters all GameObjects inside SCP-914's intake chamber into players and items. diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 1e41ad0904..5de5dd5655 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -60,11 +60,11 @@ public static class Server /// public static string Name { - get => ServerConsole._serverName; + get => ServerConsole.ServerName; set { - ServerConsole._serverName = value; - ServerConsole.singleton.RefreshServerNameSafe(); + ServerConsole.ServerName = value; + ServerConsole.Singleton.RefreshServerNameSafe(); } } diff --git a/EXILED/Exiled.API/Features/Toys/AdminToy.cs b/EXILED/Exiled.API/Features/Toys/AdminToy.cs index a3ffa539cd..4a82030c8c 100644 --- a/EXILED/Exiled.API/Features/Toys/AdminToy.cs +++ b/EXILED/Exiled.API/Features/Toys/AdminToy.cs @@ -166,6 +166,10 @@ public static AdminToy Get(AdminToyBase adminToyBase) PrimitiveObjectToy primitiveObjectToy => new Primitive(primitiveObjectToy), ShootingTarget shootingTarget => new ShootingTargetToy(shootingTarget), SpeakerToy speakerToy => new Speaker(speakerToy), + CapybaraToy capybaraToy => new Capybara(capybaraToy), + Scp079CameraToy scp079CameraToy => new CameraToy(scp079CameraToy), + InvisibleInteractableToy invisibleInteractableToy => new InteractableToy(invisibleInteractableToy), + TextToy textToy => new Text(textToy), _ => throw new System.NotImplementedException() }; } diff --git a/EXILED/Exiled.API/Features/Toys/CameraToy.cs b/EXILED/Exiled.API/Features/Toys/CameraToy.cs new file mode 100644 index 0000000000..518c4d789a --- /dev/null +++ b/EXILED/Exiled.API/Features/Toys/CameraToy.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Toys +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + using AdminToys; + using Exiled.API.Enums; + using Exiled.API.Interfaces; + using UnityEngine; + + /// + /// A wrapper class for . + /// + internal class CameraToy : AdminToy, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The of the toy. + internal CameraToy(Scp079CameraToy scp079CameraToy) + : base(scp079CameraToy, AdminToyType.CameraToy) => Base = scp079CameraToy; + + /// + /// Gets the base . + /// + public Scp079CameraToy Base { get; } + + /// + /// Gets or sets the Vertical Restriction. + /// + public Vector2 VerticalConstraint + { + get => Base.NetworkVerticalConstraint; + set => Base.NetworkVerticalConstraint = value; + } + + /// + /// Gets or sets the Horizontal restriction. + /// + public Vector2 HorizontalConstraint + { + get => Base.NetworkHorizontalConstraint; + set => Base.NetworkHorizontalConstraint = value; + } + + /// + /// Gets or sets the Zoom restriction. + /// + public Vector2 ZoomConstraint + { + get => Base.NetworkZoomConstraint; + set => Base.NetworkZoomConstraint = value; + } + + /// + /// Gets or sets the Room where the Camera is associated with. + /// + public Room Room + { + get => Room.Get(Base.NetworkRoom); + set => Base.NetworkRoom = value.Identifier; + } + + /// + /// Gets or sets the Name of the Camera. + /// + public string Name + { + get => Base.NetworkLabel; + set => Base.NetworkLabel = value; + } + } +} diff --git a/EXILED/Exiled.API/Features/Toys/Capybara.cs b/EXILED/Exiled.API/Features/Toys/Capybara.cs new file mode 100644 index 0000000000..b88dbfd9c8 --- /dev/null +++ b/EXILED/Exiled.API/Features/Toys/Capybara.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Toys +{ + using AdminToys; + using Enums; + using Exiled.API.Interfaces; + + /// + /// A wrapper class for . + /// + 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; + + /// + /// Gets the prefab. + /// + public static CapybaraToy Prefab => PrefabHelper.GetPrefab(PrefabType.CapybaraToy); + + /// + /// Gets the base . + /// + public CapybaraToy Base { get; } + + /// + /// Gets or sets a value indicating whether the capybara can be collided with. + /// + public bool Collidable + { + get => Base.Network_collisionsEnabled; + set => Base.Network_collisionsEnabled = value; + } + } +} diff --git a/EXILED/Exiled.API/Features/Toys/InteractableToy.cs b/EXILED/Exiled.API/Features/Toys/InteractableToy.cs new file mode 100644 index 0000000000..c6c73936cd --- /dev/null +++ b/EXILED/Exiled.API/Features/Toys/InteractableToy.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Toys +{ + using AdminToys; + using Exiled.API.Enums; + using Exiled.API.Interfaces; + using UnityEngine; + + using static AdminToys.InvisibleInteractableToy; + + /// + /// A wrapper class for . + /// + internal class InteractableToy : AdminToy, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The of the toy. + internal InteractableToy(InvisibleInteractableToy invisibleInteractableToy) + : base(invisibleInteractableToy, AdminToyType.InvisibleInteractableToy) => Base = invisibleInteractableToy; + + /// + /// Gets the base . + /// + public InvisibleInteractableToy Base { get; } + + /// + /// Gets or sets the Shape of the Interactable. + /// + public ColliderShape Shape + { + get => Base.NetworkShape; + set => Base.NetworkShape = value; + } + + /// + /// Gets or sets the time to interact with the Interactable. + /// + public float InteractionDuration + { + get => Base.NetworkInteractionDuration; + set => Base.NetworkInteractionDuration = value; + } + + /// + /// Gets or sets a value indicating whether the interactable is locked. + /// + public bool IsLocked + { + get => Base.NetworkIsLocked; + set => Base.NetworkIsLocked = value; + } + } +} diff --git a/EXILED/Exiled.API/Features/Toys/Light.cs b/EXILED/Exiled.API/Features/Toys/Light.cs index 9496f6b833..9e167d1bb5 100644 --- a/EXILED/Exiled.API/Features/Toys/Light.cs +++ b/EXILED/Exiled.API/Features/Toys/Light.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features.Toys { + using System; using System.Linq; using AdminToys; @@ -98,6 +99,7 @@ public Color Color /// /// Gets or sets the shape that the Light emits. /// + [Obsolete("This property has been deprecated. Use LightType.Spot, LightType.Pyramid, or LightType.Box instead.")] public LightShape LightShape { get => Base.NetworkLightShape; diff --git a/EXILED/Exiled.API/Features/Toys/Text.cs b/EXILED/Exiled.API/Features/Toys/Text.cs new file mode 100644 index 0000000000..7c3317cd22 --- /dev/null +++ b/EXILED/Exiled.API/Features/Toys/Text.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Toys +{ + using AdminToys; + using Enums; + using Exiled.API.Interfaces; + using UnityEngine; + + /// + /// A wrapper class for . + /// + public class Text : AdminToy, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The of the toy. + internal Text(TextToy speakerToy) + : base(speakerToy, AdminToyType.TextToy) => Base = speakerToy; + + /// + /// Gets the prefab. + /// + public static TextToy Prefab => PrefabHelper.GetPrefab(PrefabType.TextToy); + + /// + /// Gets the base . + /// + public TextToy Base { get; } + + /// + /// Gets or sets the Text shown. + /// + public string TextFormat + { + get => Base.Network_textFormat; + set => Base.Network_textFormat = value; + } + + /// + /// Gets or sets the size of the Display Size of the Text. + /// + public Vector2 DisplaySize + { + get => Base.Network_displaySize; + set => Base.Network_displaySize = value; + } + } +} diff --git a/EXILED/Exiled.API/Features/Warhead.cs b/EXILED/Exiled.API/Features/Warhead.cs index 22ccf2207e..97ce5a9cfe 100644 --- a/EXILED/Exiled.API/Features/Warhead.cs +++ b/EXILED/Exiled.API/Features/Warhead.cs @@ -35,7 +35,7 @@ public static class Warhead /// /// Gets the cached component. /// - public static AlphaWarheadOutsitePanel OutsitePanel => alphaWarheadOutsitePanel != null ? alphaWarheadOutsitePanel : (alphaWarheadOutsitePanel = UnityEngine.Object.FindObjectOfType()); + public static AlphaWarheadOutsitePanel OutsitePanel => alphaWarheadOutsitePanel != null ? alphaWarheadOutsitePanel : (alphaWarheadOutsitePanel = UnityEngine.Object.FindFirstObjectByType()); /// /// Gets the of the warhead lever. @@ -84,8 +84,8 @@ public static bool LeverStatus /// public static bool IsKeycardActivated { - get => OutsitePanel.NetworkkeycardEntered; - set => OutsitePanel.NetworkkeycardEntered = value; + get => AlphaWarheadActivationPanel.IsUnlocked; + set => AlphaWarheadActivationPanel.IsUnlocked = value; } /// @@ -168,7 +168,16 @@ public static int Kills public static void CloseBlastDoors() { foreach (BlastDoor door in BlastDoors) - door.SetClosed(false, true); + door.SetDoorState(true, false); + } + + /// + /// Open the surface blast doors. + /// + public static void OpenBlastDoors() + { + foreach (BlastDoor door in BlastDoors) + door.SetDoorState(false, true); } /// diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index d11fe275ef..892733246b 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -45,7 +45,7 @@ internal Window(BreakableWindow window, Room room) } /// - /// Gets a of which contains all the instances. + /// Gets a of which contains all the instances. /// public static IReadOnlyCollection List => BreakableWindowToWindow.Values; @@ -225,6 +225,7 @@ public void DamageWindow(float amount, DamageHandlerBase handler) RoomType.HczTestRoom => GlassType.TestRoom, RoomType.HczEzCheckpointA => GlassType.HczEzCheckpointA, RoomType.HczEzCheckpointB => GlassType.HczEzCheckpointB, + RoomType.Hcz127 => GlassType.Scp127, _ => GlassType.Unknown, }; } diff --git a/EXILED/Exiled.CreditTags/Exiled.CreditTags.csproj b/EXILED/Exiled.CreditTags/Exiled.CreditTags.csproj index 9382489fb3..a169d69155 100644 --- a/EXILED/Exiled.CreditTags/Exiled.CreditTags.csproj +++ b/EXILED/Exiled.CreditTags/Exiled.CreditTags.csproj @@ -27,13 +27,13 @@ + - - + diff --git a/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs b/EXILED/Exiled.CreditTags/Features/DatabaseHandler.cs index 11c6a6c2a1..c2ec515742 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/ExMod-Team/CreditTags/main/data.yml"; + private const string Url = "https://raw.githubusercontent.com/ExSLMod-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/EventArgs/OwnerEscapingEventArgs.cs b/EXILED/Exiled.CustomItems/API/EventArgs/OwnerEscapingEventArgs.cs index 2fc89ef333..54f478d7ce 100644 --- a/EXILED/Exiled.CustomItems/API/EventArgs/OwnerEscapingEventArgs.cs +++ b/EXILED/Exiled.CustomItems/API/EventArgs/OwnerEscapingEventArgs.cs @@ -30,7 +30,7 @@ public class OwnerEscapingEventArgs : EscapingEventArgs /// /// The instance. public OwnerEscapingEventArgs(Item item, EscapingEventArgs ev) - : base(ev.Player, ev.NewRole, ev.EscapeScenario) + : base(ev.Player.ReferenceHub, ev.NewRole, ev.EscapeScenario) { Item = item; } diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs b/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs index f87d473783..e6f1932905 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs @@ -8,13 +8,16 @@ namespace Exiled.CustomItems.API.Features { using System; + using System.Collections.Generic; using System.ComponentModel; using Exiled.API.Extensions; using Exiled.API.Features; using Exiled.API.Features.Items; + using Exiled.API.Structs; using Exiled.Events.EventArgs.Player; + using InventorySystem.Items.Armor; using MEC; /// @@ -55,6 +58,16 @@ public override ItemType Type [Description("The value must be above 0 and below 100")] public virtual int VestEfficacy { get; set; } = 80; + /// + /// Gets or sets the Ammunition limit the player have. + /// + public virtual List AmmoLimits { get; set; } = new(); + + /// + /// Gets or sets the Item Category limit the player have. + /// + public virtual List CategoryLimits { get; set; } = new(); + /// public override void Give(Player player, bool displayMessage = true) { @@ -66,6 +79,12 @@ public override void Give(Player player, bool displayMessage = true) armor.VestEfficacy = VestEfficacy; armor.HelmetEfficacy = HelmetEfficacy; + if (AmmoLimits.Count != 0) + armor.AmmoLimits = AmmoLimits; + + if (AmmoLimits.Count != 0) + armor.CategoryLimits = CategoryLimits; + player.AddItem(armor); TrackedSerials.Add(armor.Serial); diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs index e28f1ea3d3..d53846cbb0 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs @@ -8,6 +8,7 @@ namespace Exiled.CustomItems.API.Features { using System; + using System.Linq; using Exiled.API.Enums; using Exiled.API.Extensions; @@ -18,6 +19,8 @@ namespace Exiled.CustomItems.API.Features using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Item; using Exiled.Events.EventArgs.Player; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; using UnityEngine; /// @@ -33,16 +36,41 @@ public override ItemType Type set { if (!value.IsKeycard()) - throw new ArgumentOutOfRangeException("Type", value, "Invalid keycard type."); + throw new ArgumentOutOfRangeException(nameof(Type), value, "Invalid keycard type."); base.Type = value; } } + /// + /// Gets or sets name of keycard holder. + /// + public virtual string KeycardName { get; set; } = string.Empty; + + /// + /// Gets or sets a label for keycard. + /// + public virtual string KeycardLabel { get; set; } = string.Empty; + + /// + /// Gets or sets a color of keycard label. + /// + public virtual Color32? KeycardLabelColor { get; set; } + + /// + /// Gets or sets a tint color. + /// + public virtual Color32? TintColor { get; set; } + /// /// Gets or sets the permissions for custom keycard. /// - public virtual KeycardPermissions Permissions { get; set; } + public virtual KeycardPermissions Permissions { get; set; } = KeycardPermissions.None; + + /// + /// Gets or sets a color of keycard permissions. + /// + public virtual Color32? KeycardPermissionsColor { get; set; } /// public override void Give(Player player, Item item, bool displayMessage = true) @@ -50,18 +78,66 @@ public override void Give(Player player, Item item, bool displayMessage = true) base.Give(player, item, displayMessage); if (item.Is(out Keycard card)) - card.Permissions = Permissions; + SetupKeycard(card); } /// public override Pickup? Spawn(Vector3 position, Item item, Player? previousOwner = null) { if (item.Is(out Keycard card)) - card.Permissions = Permissions; + SetupKeycard(card); return base.Spawn(position, item, previousOwner); } + /// + /// Setups keycard according to this class. + /// + /// Item instance. + protected virtual void SetupKeycard(Keycard keycard) + { + if (!keycard.Base.Customizable) + return; + + DetailBase[] details = keycard.Base.Details; + + NametagDetail? nameDetail = details.OfType().FirstOrDefault(); + + if (nameDetail != null && !string.IsNullOrEmpty(KeycardName)) + NametagDetail._customNametag = KeycardName; + + CustomItemNameDetail? raNameDetail = details.OfType().FirstOrDefault(); + + if (raNameDetail != null) + raNameDetail.Name = Name; + + CustomLabelDetail? labelDetail = details.OfType().FirstOrDefault(); + + if (labelDetail != null) + { + if (!string.IsNullOrEmpty(KeycardLabel)) + CustomLabelDetail._customText = KeycardLabel; + + if (KeycardLabelColor.HasValue) + CustomLabelDetail._customColor = KeycardLabelColor.Value; + } + + CustomPermsDetail? permsDetail = details.OfType().FirstOrDefault(); + + if (permsDetail != null) + { + CustomPermsDetail._customLevels = new((DoorPermissionFlags)Permissions); + CustomPermsDetail._customColor = KeycardPermissionsColor; + } + + CustomTintDetail? tintDetail = details.OfType().FirstOrDefault(); + + if (tintDetail != null && TintColor.HasValue) + { + CustomTintDetail._customColor = TintColor.Value; + } + } + /// /// Called when custom keycard interacts with a door. /// diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs index e775ee3e9f..277037494e 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs @@ -52,7 +52,7 @@ public override ItemType Type /// /// Gets or sets the weapon damage. /// - public abstract float Damage { get; set; } + public virtual float Damage { get; set; } = -1; /// /// Gets or sets a value indicating how big of a clip the weapon will have. @@ -205,7 +205,7 @@ protected virtual void OnShot(ShotEventArgs ev) /// . protected virtual void OnHurting(HurtingEventArgs ev) { - if (ev.IsAllowed && Damage > 0f) + if (ev.IsAllowed && Damage >= 0) ev.Amount = Damage; } diff --git a/EXILED/Exiled.CustomItems/Exiled.CustomItems.csproj b/EXILED/Exiled.CustomItems/Exiled.CustomItems.csproj index 703a8a7c77..6d45708c17 100644 --- a/EXILED/Exiled.CustomItems/Exiled.CustomItems.csproj +++ b/EXILED/Exiled.CustomItems/Exiled.CustomItems.csproj @@ -28,9 +28,9 @@ + - diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 60d3f44d60..ba6ecc95f8 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -18,6 +18,7 @@ namespace Exiled.CustomRoles.API.Features using Exiled.API.Features; using Exiled.API.Features.Attributes; using Exiled.API.Features.Pools; + using Exiled.API.Features.Roles; using Exiled.API.Features.Spawn; using Exiled.API.Interfaces; using Exiled.CustomItems.API.Features; @@ -155,6 +156,11 @@ public abstract class CustomRole /// public virtual Vector3 Scale { get; set; } = Vector3.one; + /// + /// Gets or sets a value indicating the 's gravity. + /// + public virtual Vector3? Gravity { get; set; } + /// /// Gets or sets a containing cached and their which is cached Role with FF multiplier. /// @@ -553,7 +559,8 @@ public virtual void AddRole(Player player) player.Health = MaxHealth; player.MaxHealth = MaxHealth; player.Scale = Scale; - + if (Gravity.HasValue && player.Role is FpcRole fpcRole) + fpcRole.Gravity = Gravity.Value; Vector3 position = GetSpawnPosition(); if (position != Vector3.zero) { diff --git a/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj b/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj index d569aacb2c..78b2030580 100644 --- a/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj +++ b/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj @@ -29,9 +29,9 @@ + - diff --git a/EXILED/Exiled.Events/Commands/Config/Merge.cs b/EXILED/Exiled.Events/Commands/Config/Merge.cs index b9916ede64..c6cf79042d 100644 --- a/EXILED/Exiled.Events/Commands/Config/Merge.cs +++ b/EXILED/Exiled.Events/Commands/Config/Merge.cs @@ -49,8 +49,8 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s SortedDictionary configs = ConfigManager.LoadSorted(ConfigManager.Read()); LoaderPlugin.Config.ConfigType = ConfigType.Default; bool haveBeenSaved = ConfigManager.Save(configs); - PluginAPI.Loader.AssemblyLoader.InstalledPlugins.FirstOrDefault(x => x.PluginName == "Exiled Loader")?.SaveConfig(new LoaderPlugin(), nameof(LoaderPlugin.Config)); + LoaderPlugin.Instance.SaveConfig(); response = $"Configs have been merged successfully! Feel free to remove the directory in the following path:\n\"{Paths.IndividualConfigs}\""; return haveBeenSaved; } diff --git a/EXILED/Exiled.Events/Commands/Config/Split.cs b/EXILED/Exiled.Events/Commands/Config/Split.cs index 9fad9c3e16..34816af579 100644 --- a/EXILED/Exiled.Events/Commands/Config/Split.cs +++ b/EXILED/Exiled.Events/Commands/Config/Split.cs @@ -49,8 +49,8 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s SortedDictionary configs = ConfigManager.LoadSorted(ConfigManager.Read()); LoaderPlugin.Config.ConfigType = ConfigType.Separated; bool haveBeenSaved = ConfigManager.Save(configs); - PluginAPI.Loader.AssemblyLoader.InstalledPlugins.FirstOrDefault(x => x.PluginName == "Exiled Loader")?.SaveConfig(new LoaderPlugin(), nameof(LoaderPlugin.Config)); + LoaderPlugin.Instance.SaveConfig(); response = $"Configs have been merged successfully! Feel free to remove the file in the following path:\n\"{Paths.Config}\""; return haveBeenSaved; } diff --git a/EXILED/Exiled.Events/Commands/Hub/Hub.cs b/EXILED/Exiled.Events/Commands/Hub/Hub.cs deleted file mode 100644 index 5df93df48c..0000000000 --- a/EXILED/Exiled.Events/Commands/Hub/Hub.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Commands.Hub -{ - using System; - - using CommandSystem; - - /// - /// The EXILED hub command. - /// - [CommandHandler(typeof(RemoteAdminCommandHandler))] - [CommandHandler(typeof(GameConsoleCommandHandler))] - public class Hub : ParentCommand - { - /// - /// Initializes a new instance of the class. - /// - public Hub() - { - LoadGeneratedCommands(); - } - - /// - public override string Command { get; } = "hub"; - - /// - public override string[] Aliases { get; } = Array.Empty(); - - /// - public override string Description { get; } = "The EXILED hub command."; - - /// - public override void LoadGeneratedCommands() - { - RegisterCommand(Install.Instance); - } - - /// - protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) - { - response = "Please, specify a valid subcommand! Available ones: install"; - return false; - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs b/EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs deleted file mode 100644 index 8733468300..0000000000 --- a/EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Commands.Hub.HubApi -{ - using System; - using System.IO; - using System.Net.Http; - using System.Reflection; - using System.Threading.Tasks; - - using Exiled.Events.Commands.Hub.HubApi.Models; - - using Utf8Json; - - /// - /// An API bridge to EXILED Hub. - /// - public static class ApiProvider - { - /// - /// The API endpoint to get the plugin installation data. - /// - private const string InstallApiEndpoint = "https://hub.exiled-team.net/api/install?name="; - - /// - /// Gets installation data of the plugin by name. - /// - /// The name of plugin. - /// The . - /// A instance containing installation data. - public static async Task GetInstallationData(string pluginName, HttpClient client) - { - string url = InstallApiEndpoint + pluginName; - using HttpResponseMessage response = await client.GetAsync(url).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - using Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return JsonSerializer.Deserialize(stream); - } - - return null; - } - - /// - /// Creates a HTTP client for EXILED Hub API. - /// - /// Created HTTP client. - internal static HttpClient CreateClient() - { - HttpClient client = new(); - - client.Timeout = TimeSpan.FromSeconds(460); - client.DefaultRequestHeaders.Add("User-Agent", $"Exiled.Events (https://github.com/ExMod-Team/EXILED, {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)})"); - - return client; - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs b/EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs deleted file mode 100644 index 7d0a7b8f00..0000000000 --- a/EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Commands.Hub.HubApi.Models -{ - using System.Runtime.Serialization; - - using Utf8Json; - - /// - /// A struct containing all hub plugin data. - /// - public readonly struct HubPlugin : IJsonSerializable - { - /// - /// The repository id. - /// - [DataMember(Name = "repositoryId")] - public readonly long RepositoryId; - - /// - /// Initializes a new instance of the struct. - /// - /// - [SerializationConstructor] - public HubPlugin(long repositoryId) - { - RepositoryId = repositoryId; - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Commands/Hub/Install.cs b/EXILED/Exiled.Events/Commands/Hub/Install.cs deleted file mode 100644 index 323a230108..0000000000 --- a/EXILED/Exiled.Events/Commands/Hub/Install.cs +++ /dev/null @@ -1,118 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Commands.Hub -{ - using System; - using System.IO; - using System.Linq; - using System.Net.Http; - - using CommandSystem; - - using Exiled.API.Features; - using Exiled.Events.Commands.Hub.HubApi.Models; - using Exiled.Loader; - using Exiled.Loader.GHApi; - using Exiled.Loader.GHApi.Models; - using Exiled.Loader.GHApi.Settings; - using Exiled.Permissions.Extensions; - - using RemoteAdmin; - - /// - /// The command to install a plugin from EXILED Hub. - /// - public class Install : ICommand, IUsageProvider - { - /// - /// Gets static instance of the command. - /// - public static Install Instance { get; } = new(); - - /// - public string Command { get; } = "install"; - - /// - public string[] Aliases { get; } = { "i" }; - - /// - public string[] Usage { get; } = { "Plugin name", "Release tag (optional)" }; - - /// - public string Description { get; } = "Installs a plugin from EXILED Hub."; - - /// - public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) - { - const string permission = "hub.install"; - - if (!sender.CheckPermission(permission) && sender is PlayerCommandSender playerSender && !playerSender.FullPermissions) - { - response = $"You don't have permissions to install the plugins. Required permission node: \"{permission}\"."; - return false; - } - - if (arguments.Count == 0) - { - response = "Missing arguments! Usage: hub install (release tag)"; - return false; - } - - using HttpClient client = HubApi.ApiProvider.CreateClient(); - - HubPlugin? pluginData = HubApi.ApiProvider.GetInstallationData(arguments.At(0), client).GetAwaiter().GetResult(); - - if (pluginData == null) - { - response = "An error has occurred while fetching the plugin data. Please check if the plugin name is correct and try again."; - return false; - } - - Release[] pluginReleases = client.GetReleases(pluginData.Value.RepositoryId, new GetReleasesSettings(50, 1)).GetAwaiter().GetResult(); - Release releaseToDownload = pluginReleases[0]; - - if (arguments.Count > 1) - { - Release foundRelease = pluginReleases.FirstOrDefault(x => x.TagName == arguments.At(1)); - - if (foundRelease.Id == 0) - { - response = "Release with the provided tag not found."; - return false; - } - - releaseToDownload = foundRelease; - } - - ReleaseAsset[] releaseAssets = releaseToDownload.Assets.Where(x => x.Name.IndexOf("nwapi", StringComparison.OrdinalIgnoreCase) == -1).ToArray(); - - Log.Info($"Downloading release \"{releaseToDownload.TagName}\". Found {releaseAssets.Length} asset(s) to download."); - - foreach (ReleaseAsset asset in releaseAssets) - { - Log.Info($"Downloading asset {asset.Name}. Asset size: {Math.Round(asset.Size / 1000f, 2)} KB."); - using HttpResponseMessage assetResponse = client.GetAsync(asset.BrowserDownloadUrl).ConfigureAwait(false).GetAwaiter().GetResult(); - - string pluginPath = Path.Combine(Paths.Plugins, asset.Name); - - if (File.Exists(pluginPath) && Environment.OSVersion.Platform == PlatformID.Unix) - LinuxPermission.SetFileUserAndGroupReadWriteExecutePermissions(pluginPath); - - using Stream stream = assetResponse.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - using FileStream fileStream = new(pluginPath, FileMode.Create, FileAccess.Write, FileShare.None); - stream.CopyToAsync(fileStream).ConfigureAwait(false).GetAwaiter().GetResult(); - - if (Environment.OSVersion.Platform == PlatformID.Unix) - LinuxPermission.SetFileUserAndGroupReadWriteExecutePermissions(pluginPath); - } - - response = $"{arguments.At(0)} has been successfully installed."; - return true; - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Config.cs b/EXILED/Exiled.Events/Config.cs index 56a8206e74..fd244f699b 100644 --- a/EXILED/Exiled.Events/Config.cs +++ b/EXILED/Exiled.Events/Config.cs @@ -77,6 +77,7 @@ public sealed class Config : IConfig /// /// Gets or sets a value indicating whether keycard throw can affect basic doors. /// + /// TODO: Make a poll about removing this config. (unimplemented since 9.6.0-beta7) [Description("Indicates whether thrown keycards can affect doors that don't require any permissions")] public bool CanKeycardThrowAffectDoors { get; set; } = false; diff --git a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs index e39a463c23..a8b986c171 100644 --- a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs @@ -26,12 +26,20 @@ public class SendingCassieMessageEventArgs : IDeniableEvent /// /// /// + /// + /// + /// + /// + /// + /// /// Indicates whether the event can be executed. - public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise, bool isAllowed = true) + public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise, bool customAnnouncement, string customSubtitles, bool isAllowed = true) { Words = words; + CustomSubtitles = customSubtitles; MakeHold = makeHold; MakeNoise = makeNoise; + IsCustomAnnouncement = customAnnouncement; IsAllowed = isAllowed; } @@ -40,6 +48,11 @@ public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise /// public string Words { get; set; } + /// + /// Gets or sets the message subtitles. + /// + public string CustomSubtitles { get; set; } + /// /// Gets or sets a value indicating whether the message should be held. /// @@ -54,5 +67,10 @@ public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise /// Gets or sets a value indicating whether the message can be sent. /// public bool IsAllowed { get; set; } + + /// + /// Gets or sets a value indicating whether the message can be sent. + /// + public bool IsCustomAnnouncement { get; set; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs index a083ac1fa1..4c43eb1df8 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs @@ -30,17 +30,14 @@ public class AnnouncingScpTerminationEventArgs : IAttackerEvent, IDeniableEvent /// /// /// - /// - /// - /// - public AnnouncingScpTerminationEventArgs(Player scp, DamageHandlerBase damageHandlerBase, bool isAllowed = true) + public AnnouncingScpTerminationEventArgs(Player scp, DamageHandlerBase damageHandlerBase) { Player = scp; Role = scp.Role; DamageHandler = new CustomDamageHandler(scp, damageHandlerBase); Attacker = DamageHandler.BaseIs(out CustomAttackerHandler customAttackerHandler) ? customAttackerHandler.Attacker : null; TerminationCause = damageHandlerBase.CassieDeathAnnouncement.Announcement; - IsAllowed = isAllowed; + IsAllowed = true; } /// @@ -71,6 +68,6 @@ public AnnouncingScpTerminationEventArgs(Player scp, DamageHandlerBase damageHan /// /// Gets or sets a value indicating whether the SCP termination will be announced by C.A.S.S.I.E. /// - public bool IsAllowed { get; set; } = true; + public bool IsAllowed { get; set; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs index 66043379d5..205500cf60 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs @@ -41,7 +41,7 @@ public FillingLockerEventArgs(ItemPickupBase pickupBase, LockerChamber lockerCha /// /// Gets a locker which is containing . /// - public API.Features.Lockers.Locker Locker => Chamber.Locker; + public API.Features.Lockers.Locker Locker => Chamber?.Locker; /// /// Gets a chamber which is filling. diff --git a/EXILED/Exiled.Events/EventArgs/Player/ActivatingGeneratorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ActivatingGeneratorEventArgs.cs index 33a6e87ee5..8050e534bf 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ActivatingGeneratorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ActivatingGeneratorEventArgs.cs @@ -27,14 +27,11 @@ public class ActivatingGeneratorEventArgs : IPlayerEvent, IGeneratorEvent, IDeni /// /// /// - /// - /// - /// - public ActivatingGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed = true) + public ActivatingGeneratorEventArgs(Player player, Scp079Generator generator) { Player = player; Generator = Generator.Get(generator); - IsAllowed = isAllowed; + IsAllowed = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/ActivatingWarheadPanelEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ActivatingWarheadPanelEventArgs.cs index f31eded385..65504085ed 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ActivatingWarheadPanelEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ActivatingWarheadPanelEventArgs.cs @@ -25,7 +25,7 @@ public class ActivatingWarheadPanelEventArgs : IPlayerEvent, IDeniableEvent /// /// /// - public ActivatingWarheadPanelEventArgs(Player player, bool isAllowed = true) + public ActivatingWarheadPanelEventArgs(Player player, bool isAllowed) { Player = player; IsAllowed = isAllowed; diff --git a/EXILED/Exiled.Events/EventArgs/Player/CancelledItemUseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/CancelledItemUseEventArgs.cs index 33719e6217..19435c5250 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/CancelledItemUseEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/CancelledItemUseEventArgs.cs @@ -10,6 +10,7 @@ namespace Exiled.Events.EventArgs.Player using API.Features; using Exiled.API.Features.Items; using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Usables; /// /// Contains all information before a player cancels usage of an item. @@ -19,14 +20,16 @@ public class CancelledItemUseEventArgs : IPlayerEvent, IUsableEvent /// /// Initializes a new instance of the class. /// - /// The player who's stopping the use of an item. - /// + /// + /// + /// + /// /// /// - public CancelledItemUseEventArgs(Player player, Item item) + public CancelledItemUseEventArgs(ReferenceHub hub, UsableItem usableItem) { - Player = player; - Usable = item is Usable usable ? usable : null; + Player = Player.Get(hub); + Usable = Item.Get(usableItem); } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs index 1caaabb0a6..a030f171c2 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/CancellingItemUseEventArgs.cs @@ -20,13 +20,15 @@ public class CancellingItemUseEventArgs : IPlayerEvent, IDeniableEvent, IUsableE /// /// Initializes a new instance of the class. /// - /// The player who's stopping the use of an item. + /// + /// + /// /// /// /// - public CancellingItemUseEventArgs(Player player, UsableItem item) + public CancellingItemUseEventArgs(ReferenceHub hub, UsableItem item) { - Player = player; + Player = Player.Get(hub); Usable = Item.Get(item); } diff --git a/EXILED/Exiled.Events/EventArgs/Player/ClosingGeneratorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ClosingGeneratorEventArgs.cs index 9218db494b..c33b0bcc0f 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ClosingGeneratorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ClosingGeneratorEventArgs.cs @@ -21,12 +21,11 @@ public class ClosingGeneratorEventArgs : IPlayerEvent, IDeniableEvent, IGenerato /// /// The player who's closing the generator. /// The instance. - /// Indicates whether the generator can be closed. - public ClosingGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed = true) + public ClosingGeneratorEventArgs(Player player, Scp079Generator generator) { Player = player; Generator = Generator.Get(generator); - IsAllowed = isAllowed; + IsAllowed = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/DeactivatingWorkstationEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DeactivatingWorkstationEventArgs.cs index 48b7164d83..20ce6f84d0 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DeactivatingWorkstationEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DeactivatingWorkstationEventArgs.cs @@ -31,7 +31,7 @@ public class DeactivatingWorkstationEventArgs : IPlayerEvent, IDeniableEvent /// public DeactivatingWorkstationEventArgs(WorkstationController controller, bool isAllowed = true) { - Player = Player.Get(controller._knownUser); + Player = Player.Get(controller.KnownUser); WorkstationController = controller; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index a10eea6922..4a4fa0dcba 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs @@ -25,7 +25,7 @@ public class EscapingEventArgs : IPlayerEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// @@ -34,12 +34,12 @@ public class EscapingEventArgs : IPlayerEvent, IDeniableEvent /// /// /// - public EscapingEventArgs(Player player, RoleTypeId newRole, EscapeScenario escapeScenario) + public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeScenario escapeScenario) { - Player = player; + Player = Player.Get(referenceHub); NewRole = newRole; EscapeScenario = escapeScenario; - IsAllowed = escapeScenario is not EscapeScenario.CustomEscape; + IsAllowed = escapeScenario is not EscapeScenario.None and not EscapeScenario.CustomEscape; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingPocketDimensionEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingPocketDimensionEventArgs.cs index 375eed5302..841edfdcf6 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingPocketDimensionEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingPocketDimensionEventArgs.cs @@ -21,18 +21,27 @@ public class EscapingPocketDimensionEventArgs : IPlayerEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// + /// + /// + /// + /// /// /// /// /// /// - public EscapingPocketDimensionEventArgs(Player player, Vector3 position) + public EscapingPocketDimensionEventArgs(PocketDimensionTeleport pocketDimensionTeleport, ReferenceHub hub, Vector3 position) { - Player = player; + Teleporter = pocketDimensionTeleport; + Player = Player.Get(hub); TeleportPosition = position; } + /// + /// Gets the PocketDimensionTeleport the player walked into. + /// + public PocketDimensionTeleport Teleporter { get; } + /// /// Gets the player who's escaping the pocket dimension. /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/FailingEscapePocketDimensionEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/FailingEscapePocketDimensionEventArgs.cs index 247e62cbe7..620178368c 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/FailingEscapePocketDimensionEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/FailingEscapePocketDimensionEventArgs.cs @@ -7,6 +7,8 @@ namespace Exiled.Events.EventArgs.Player { + using System; + using API.Features; using Interfaces; @@ -19,19 +21,19 @@ public class FailingEscapePocketDimensionEventArgs : IPlayerEvent, IDeniableEven /// /// Initializes a new instance of the class. /// - /// - /// - /// - /// + /// /// /// + /// + /// + /// /// /// /// - public FailingEscapePocketDimensionEventArgs(Player player, PocketDimensionTeleport teleporter, bool isAllowed = true) + public FailingEscapePocketDimensionEventArgs(PocketDimensionTeleport pocketDimensionTeleport, ReferenceHub hub, bool isAllowed = true) { - Player = player; - Teleporter = teleporter; + Player = Player.Get(hub); + Teleporter = pocketDimensionTeleport; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/FlippingCoinEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/FlippingCoinEventArgs.cs index 369a443868..954d2f39e8 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/FlippingCoinEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/FlippingCoinEventArgs.cs @@ -20,7 +20,7 @@ public class FlippingCoinEventArgs : IPlayerEvent, IDeniableEvent, IItemEvent /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// @@ -29,9 +29,9 @@ public class FlippingCoinEventArgs : IPlayerEvent, IDeniableEvent, IItemEvent /// /// /// - public FlippingCoinEventArgs(Player player, Coin coin, bool isTails) + public FlippingCoinEventArgs(ReferenceHub referenceHub, Coin coin, bool isTails) { - Player = player; + Player = Player.Get(referenceHub); Item = Item.Get(coin); IsTails = isTails; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs index 1b97b4a946..8fd0eab99b 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs @@ -23,7 +23,7 @@ public class HurtEventArgs : IAttackerEvent /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// @@ -32,11 +32,11 @@ public class HurtEventArgs : IAttackerEvent /// /// /// - public HurtEventArgs(Player target, DamageHandlerBase damageHandler, DamageHandlerBase.HandlerOutput handlerOutput) + public HurtEventArgs(ReferenceHub referenceHub, DamageHandlerBase damageHandler, DamageHandlerBase.HandlerOutput handlerOutput) { - DamageHandler = new CustomDamageHandler(target, damageHandler); + Player = Player.Get(referenceHub); + DamageHandler = new CustomDamageHandler(Player, damageHandler); Attacker = DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler) ? attackerDamageHandler.Attacker : null; - Player = target; HandlerOutput = handlerOutput; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs index 75eb562f39..c93656d6fc 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs @@ -33,17 +33,14 @@ public class InteractingDoorEventArgs : IPlayerEvent, IDoorEvent, IDeniableEvent /// /// /// - /// - /// - /// - public InteractingDoorEventArgs(Player player, DoorVariant door, byte colliderId, bool isAllowed = true, bool canInteract = true) + public InteractingDoorEventArgs(Player player, DoorVariant door, byte colliderId, bool isAllowed) { Player = player; Door = Door.Get(door); ColliderId = colliderId; Collider = InteractableCollider.TryGetCollider(door, colliderId, out InteractableCollider interactableCollider) ? interactableCollider : null; IsAllowed = isAllowed; - CanInteract = canInteract; + CanInteract = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/OpeningGeneratorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/OpeningGeneratorEventArgs.cs index f69507e355..13dc43febf 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/OpeningGeneratorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/OpeningGeneratorEventArgs.cs @@ -27,14 +27,11 @@ public class OpeningGeneratorEventArgs : IPlayerEvent, IDeniableEvent, IGenerato /// /// /// - /// - /// - /// - public OpeningGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed = true) + public OpeningGeneratorEventArgs(Player player, Scp079Generator generator) { Player = player; Generator = Generator.Get(generator); - IsAllowed = isAllowed; + IsAllowed = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/PickingUpItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/PickingUpItemEventArgs.cs index 90493c03c5..f71781e3b7 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/PickingUpItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/PickingUpItemEventArgs.cs @@ -21,7 +21,7 @@ public class PickingUpItemEventArgs : IPlayerEvent, IPickupEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// @@ -30,10 +30,10 @@ public class PickingUpItemEventArgs : IPlayerEvent, IPickupEvent, IDeniableEvent /// /// /// - public PickingUpItemEventArgs(Player player, ItemPickupBase pickup, bool isAllowed = true) + public PickingUpItemEventArgs(ReferenceHub referenceHub, ItemPickupBase pickup, bool isAllowed = true) { IsAllowed = isAllowed; - Player = player; + Player = Player.Get(referenceHub); Pickup = Pickup.Get(pickup); } diff --git a/EXILED/Exiled.Events/EventArgs/Player/PreAuthenticatingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/PreAuthenticatingEventArgs.cs index f2af6a8300..3c127ca02e 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/PreAuthenticatingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/PreAuthenticatingEventArgs.cs @@ -13,7 +13,6 @@ namespace Exiled.Events.EventArgs.Player using LiteNetLib; using LiteNetLib.Utils; - using PluginAPI.Events; #pragma warning disable SA1600 //TODO: #pragma warning disable SA1309 diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs index 47861a038c..d571f1e451 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs @@ -13,21 +13,24 @@ namespace Exiled.Events.EventArgs.Player /// /// Contains all information when checking if a player has a reserved slot. /// - public class ReservedSlotsCheckEventArgs : IExiledEvent + public class ReservedSlotsCheckEventArgs : IExiledEvent, IDeniableEvent { + private ReservedSlotEventResult reservedSlotEventResult = ReservedSlotEventResult.UseBaseGameSystem; + /// /// Initializes a new instance of the class. /// - /// - /// - /// /// /// /// - public ReservedSlotsCheckEventArgs(string userId, bool hasReservedSlot) + /// + /// + /// + public ReservedSlotsCheckEventArgs(bool hasReservedSlot, string userId) { UserId = userId; HasReservedSlot = hasReservedSlot; + IsAllowed = hasReservedSlot; } /// @@ -40,9 +43,36 @@ public ReservedSlotsCheckEventArgs(string userId, bool hasReservedSlot) /// public bool HasReservedSlot { get; } + /// + /// Gets or sets a value indicating whether the player is allowed to connect. + /// + public bool IsAllowed { get; set; } + /// /// Gets or sets the event result. /// - public ReservedSlotEventResult Result { get; set; } = ReservedSlotEventResult.UseBaseGameSystem; + public ReservedSlotEventResult Result + { + get => reservedSlotEventResult; + set + { + switch (reservedSlotEventResult) + { + case ReservedSlotEventResult.CanUseReservedSlots or ReservedSlotEventResult.UseBaseGameSystem: + IsAllowed = HasReservedSlot; + break; + case ReservedSlotEventResult.AllowConnectionUnconditionally: + IsAllowed = true; + break; + case ReservedSlotEventResult.CannotUseReservedSlots: + IsAllowed = false; + break; + default: + return; + } + + reservedSlotEventResult = value; + } + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/SearchingPickupEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SearchingPickupEventArgs.cs index c714df950a..fe17eb979a 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SearchingPickupEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SearchingPickupEventArgs.cs @@ -37,7 +37,7 @@ public class SearchingPickupEventArgs : IPlayerEvent, IPickupEvent, IDeniableEve /// /// /// - public SearchingPickupEventArgs(Player player, ItemPickupBase pickup, SearchSession searchSession, SearchCompletor searchCompletor, float searchTime) + public SearchingPickupEventArgs(Player player, ItemPickupBase pickup, SearchSession searchSession, PickupSearchCompletor searchCompletor, float searchTime) { Player = player; Pickup = Pickup.Get(pickup); @@ -54,7 +54,7 @@ public SearchingPickupEventArgs(Player player, ItemPickupBase pickup, SearchSess /// /// Gets or sets the SearchCompletor. /// - public SearchCompletor SearchCompletor { get; set; } + public PickupSearchCompletor SearchCompletor { get; set; } /// /// Gets or sets the Pickup search duration. diff --git a/EXILED/Exiled.Events/EventArgs/Player/SendingValidCommandEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SendingValidCommandEventArgs.cs index a3fa2d5310..1ea8a6df71 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SendingValidCommandEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SendingValidCommandEventArgs.cs @@ -11,7 +11,7 @@ namespace Exiled.Events.EventArgs.Player using Exiled.API.Features; using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Interfaces; - using PluginAPI.Enums; + using LabApi.Features.Enums; using RemoteAdmin; /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/SentValidCommandEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SentValidCommandEventArgs.cs index 0fb7778a16..1c96ff88f9 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SentValidCommandEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SentValidCommandEventArgs.cs @@ -11,7 +11,7 @@ namespace Exiled.Events.EventArgs.Player using Exiled.API.Features; using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Interfaces; - using PluginAPI.Enums; + using LabApi.Features.Enums; using RemoteAdmin; /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs index 0eb6a5c69d..18d2303353 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs @@ -25,15 +25,16 @@ public class ShotEventArgs : IPlayerEvent, IFirearmEvent /// Raycast hit info. /// The firearm used. /// The IDestructible that was hit. Can be null. - public ShotEventArgs(HitscanHitregModuleBase hitregModule, RaycastHit hitInfo, InventorySystem.Items.Firearms.Firearm firearm, IDestructible destructible) + /// + public ShotEventArgs(HitscanHitregModuleBase hitregModule, RaycastHit hitInfo, InventorySystem.Items.Firearms.Firearm firearm, IDestructible destructible, float damage) { HitregModule = hitregModule; RaycastHit = hitInfo; Destructible = destructible; Firearm = Item.Get(firearm); + Damage = damage; Player = Firearm.Owner; - Damage = Destructible is not null ? HitregModule.DamageAtDistance(hitInfo.distance) : 0f; if (Destructible is HitboxIdentity hitboxIdentity) { diff --git a/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs index e12dc914a1..9ec7423835 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs @@ -8,7 +8,6 @@ namespace Exiled.Events.EventArgs.Player { using API.Features; - using Interfaces; using PlayerRoles; @@ -16,6 +15,7 @@ namespace Exiled.Events.EventArgs.Player using PlayerStatsSystem; using UnityEngine; + using YamlDotNet.Core.Tokens; /// /// Contains all information before spawning a player ragdoll. @@ -45,7 +45,7 @@ public SpawningRagdollEventArgs(RagdollData info, bool isAllowed = true) public Vector3 Position { get => Info.StartPosition; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, value, Rotation); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, value, Rotation, Scale, Nickname, CreationTime); } /// @@ -54,13 +54,22 @@ public Vector3 Position public Quaternion Rotation { get => Info.StartRotation; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Position, value); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, value, Scale, Nickname, CreationTime); + } + + /// + /// Gets or sets the ragdoll's scale with RagdollData. + /// + public Vector3 Scale + { + get => Info.Scale; + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, Rotation, Vector3.Scale(value, RagdollManager.GetDefaultScale(Role)), Nickname, CreationTime); } /// - /// Gets or sets the ragdoll's scale. + /// Gets or sets the ragdoll's scale with GameObject. /// - public Vector3 Scale { get; set; } + public Vector3 RagdollScale { get; set; } = Vector3.one; /// /// Gets or sets the ragdoll's . @@ -68,7 +77,7 @@ public Quaternion Rotation public RoleTypeId Role { get => Info.RoleType; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, value, Position, Rotation, Nickname, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, value, Position, Rotation, Scale, Nickname, CreationTime); } /// @@ -82,7 +91,7 @@ public RoleTypeId Role public string Nickname { get => Info.Nickname; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, Rotation, value, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, Rotation, Scale, value, CreationTime); } /// @@ -96,7 +105,7 @@ public string Nickname public DamageHandlerBase DamageHandlerBase { get => Info.Handler; - set => Info = new RagdollData(Player.ReferenceHub, value, Role, Position, Rotation, Nickname, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, value, Role, Position, Rotation, Scale, Nickname, CreationTime); } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/StoppingGeneratorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/StoppingGeneratorEventArgs.cs index 53f0d9ecf5..6462c655d8 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/StoppingGeneratorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/StoppingGeneratorEventArgs.cs @@ -21,12 +21,11 @@ public class StoppingGeneratorEventArgs : IPlayerEvent, IGeneratorEvent, IDeniab /// /// The player who's flipping the switch. /// The instance. - /// Indicates whether the switch of the generator can be flipped. - public StoppingGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed = true) + public StoppingGeneratorEventArgs(Player player, Scp079Generator generator) { Player = player; Generator = Generator.Get(generator); - IsAllowed = isAllowed; + IsAllowed = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/TogglingNoClipEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/TogglingNoClipEventArgs.cs index 78144567ec..65ea273731 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/TogglingNoClipEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/TogglingNoClipEventArgs.cs @@ -19,7 +19,7 @@ public class TogglingNoClipEventArgs : IPlayerEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// @@ -28,9 +28,9 @@ public class TogglingNoClipEventArgs : IPlayerEvent, IDeniableEvent /// /// /// - public TogglingNoClipEventArgs(Player player, bool newValue, bool isAllowed = true) + public TogglingNoClipEventArgs(ReferenceHub referenceHub, bool newValue, bool isAllowed = true) { - Player = player; + Player = Player.Get(referenceHub); IsEnabled = newValue; IsAllowed = isAllowed; } @@ -41,9 +41,9 @@ public TogglingNoClipEventArgs(Player player, bool newValue, bool isAllowed = tr public Player Player { get; } /// - /// Gets or sets a value indicating whether the noclip mode will be enabled. + /// Gets a value indicating whether the noclip mode will be enabled. /// - public bool IsEnabled { get; set; } + public bool IsEnabled { get; } /// /// Gets or sets a value indicating whether the player can toggle noclip. diff --git a/EXILED/Exiled.Events/EventArgs/Player/UnlockingGeneratorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UnlockingGeneratorEventArgs.cs index 422def506f..77541732d0 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UnlockingGeneratorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UnlockingGeneratorEventArgs.cs @@ -30,7 +30,7 @@ public class UnlockingGeneratorEventArgs : IPlayerEvent, IGeneratorEvent, IDenia /// /// /// - public UnlockingGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed = true) + public UnlockingGeneratorEventArgs(Player player, Scp079Generator generator, bool isAllowed) { Player = player; Generator = Generator.Get(generator); diff --git a/EXILED/Exiled.Events/EventArgs/Player/UsingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UsingItemEventArgs.cs index 6165eb35c3..317d6ddbeb 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UsingItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UsingItemEventArgs.cs @@ -21,16 +21,18 @@ public class UsingItemEventArgs : IPlayerEvent, IDeniableEvent, IUsableEvent /// /// Initializes a new instance of the class. /// - /// The player who's going to use the item. + /// + /// + /// /// /// /// /// /// /// - public UsingItemEventArgs(Player player, UsableItem item, float cooldown) + public UsingItemEventArgs(ReferenceHub hub, UsableItem item, float cooldown) { - Player = player; + Player = Player.Get(hub); Usable = Item.Get(item) is Usable usable ? usable : null; Cooldown = cooldown; } diff --git a/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs index ae1995da47..3b3af1c73f 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UsingRadioBatteryEventArgs.cs @@ -25,19 +25,16 @@ public class UsingRadioBatteryEventArgs : IPlayerEvent, IDeniableEvent, IItemEve /// /// /// - /// - /// - /// /// /// /// /// /// /// - public UsingRadioBatteryEventArgs(RadioItem radio, Player player, float drain, bool isAllowed = true) + public UsingRadioBatteryEventArgs(RadioItem radio, float drain, bool isAllowed = true) { Radio = Item.Get(radio); - Player = player; + Player = Radio.Owner; Drain = drain; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Scp049/ActivatingSenseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp049/ActivatingSenseEventArgs.cs index c8ae4fdae2..a6144bbb8c 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp049/ActivatingSenseEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp049/ActivatingSenseEventArgs.cs @@ -29,8 +29,8 @@ public ActivatingSenseEventArgs(Player player, Player target, bool isAllowed = t Scp049 = player.Role.As(); Target = target; IsAllowed = isAllowed; - FailedCooldown = (float)Scp049.SenseAbilityFailCooldown; - Duration = (float)Scp049.SenseAbilityDuration; + FailedCooldown = PlayerRoles.PlayableScps.Scp049.Scp049SenseAbility.AttemptFailCooldown; + Duration = PlayerRoles.PlayableScps.Scp049.Scp049SenseAbility.EffectDuration; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp049/SendingCallEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp049/SendingCallEventArgs.cs index 2d302846cf..e859023690 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp049/SendingCallEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp049/SendingCallEventArgs.cs @@ -25,7 +25,7 @@ public SendingCallEventArgs(Player player, bool isAllowed = true) { Player = player; Scp049 = Player.Role.As(); - Duration = (float)Scp049.CallAbilityBaseCooldown; + Duration = PlayerRoles.PlayableScps.Scp049.Scp049CallAbility.BaseCooldown; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Scp0492/ConsumingCorpseEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp0492/ConsumingCorpseEventArgs.cs index 008ef83591..06de15a086 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp0492/ConsumingCorpseEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp0492/ConsumingCorpseEventArgs.cs @@ -25,10 +25,8 @@ public class ConsumingCorpseEventArgs : IScp0492Event, IRagdollEvent, IDeniableE /// /// /// - /// /// See for all ragdolls consumed. - // TODO: remove isAllowed argument - public ConsumingCorpseEventArgs(ReferenceHub player, BasicRagdoll ragDoll, ZombieConsumeAbility.ConsumeError error, bool isAllowed = true) + public ConsumingCorpseEventArgs(ReferenceHub player, BasicRagdoll ragDoll, ZombieConsumeAbility.ConsumeError error) { Player = Player.Get(player); Scp0492 = Player.Role.As(); diff --git a/EXILED/Exiled.Events/EventArgs/Scp173/PlacingTantrumEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp173/PlacingTantrumEventArgs.cs index 459befc3b5..70ed06bdf4 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp173/PlacingTantrumEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp173/PlacingTantrumEventArgs.cs @@ -7,6 +7,8 @@ namespace Exiled.Events.EventArgs.Scp173 { + using System; + using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; @@ -39,7 +41,9 @@ public PlacingTantrumEventArgs(Player player, TantrumEnvironmentalHazard tantrum { Player = player; Scp173 = Player.Role.As(); +#pragma warning disable CS0618 TantrumHazard = tantrumHazard; +#pragma warning restore CS0618 Cooldown = cooldown; IsAllowed = isAllowed; } @@ -52,6 +56,7 @@ public PlacingTantrumEventArgs(Player player, TantrumEnvironmentalHazard tantrum /// /// Gets the . /// + [Obsolete("This propperty is always null")] public TantrumEnvironmentalHazard TantrumHazard { get; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs index dc39d420c5..d706ea289d 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs @@ -21,31 +21,29 @@ public class InteractingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableE /// /// Initializes a new instance of the class. /// - /// + /// /// /// /// /// /// - public InteractingScp330EventArgs(Player player, int usage) + /// + /// + /// + /// + /// + /// + /// + /// + /// + public InteractingScp330EventArgs(ReferenceHub referenceHub, int usage, bool shouldPlaySound, bool shouldSever, CandyKindID candy) { - Player = player; + Player = Player.Get(referenceHub); UsageCount = usage; ShouldSever = usage >= 2; - ShouldPlaySound = true; + ShouldPlaySound = shouldPlaySound; IsAllowed = Player.IsHuman; Candy = Scp330Candies.GetRandom(); - - if (Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag)) - { - Scp330 = (Scp330)Item.Get(scp330Bag); - } - else - { - Scp330 = (Scp330)Item.Create(ItemType.SCP330, player); - Scp330.RemoveAllCandy(); - player.AddItem(Scp330); - } } /// diff --git a/EXILED/Exiled.Events/EventArgs/Server/EndingRoundEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/EndingRoundEventArgs.cs index 216676772b..2e5c11dc05 100644 --- a/EXILED/Exiled.Events/EventArgs/Server/EndingRoundEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Server/EndingRoundEventArgs.cs @@ -7,6 +7,8 @@ namespace Exiled.Events.EventArgs.Server { + using System; + using API.Enums; using Interfaces; @@ -24,17 +26,13 @@ public class EndingRoundEventArgs : IDeniableEvent /// /// /// - /// - /// - /// /// /// /// - public EndingRoundEventArgs(LeadingTeam leadingTeam, RoundSummary.SumInfo_ClassList classList, bool isForceEnded, bool isAllowed) + public EndingRoundEventArgs(LeadingTeam leadingTeam, RoundSummary.SumInfo_ClassList classList, bool isAllowed) { LeadingTeam = leadingTeam; ClassList = classList; - IsForceEnded = isForceEnded; IsAllowed = isAllowed; } @@ -51,7 +49,12 @@ public EndingRoundEventArgs(LeadingTeam leadingTeam, RoundSummary.SumInfo_ClassL /// /// Gets or sets a value indicating whether the round is ended by API call. /// - public bool IsForceEnded { get; set; } + [Obsolete("This event is now call only when it's haven't been force eneded")] + public bool IsForceEnded + { + get => false; // This event is now call only when ForceEnd method haven't been called + set => IsAllowed = value; + } /// /// Gets or sets a value indicating whether the round is going to finish or not. diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index ac37f5528a..103ba58829 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -20,7 +20,7 @@ namespace Exiled.Events using InventorySystem.Items.Usables; using PlayerRoles.Ragdolls; using PlayerRoles.RoleAssign; - using PluginAPI.Events; + using Respawning; using UnityEngine.SceneManagement; using UserSettings.ServerSpecific; @@ -81,11 +81,13 @@ public override void OnEnabled() RagdollManager.OnRagdollRemoved += Handlers.Internal.RagdollList.OnRemovedRagdoll; ItemPickupBase.OnPickupAdded += Handlers.Internal.PickupEvent.OnSpawnedPickup; ItemPickupBase.OnPickupDestroyed += Handlers.Internal.PickupEvent.OnRemovedPickup; - ServerConsole.ReloadServerName(); + + AdminToys.AdminToyBase.OnAdded += Handlers.Internal.AdminToyList.OnAddedAdminToys; + AdminToys.AdminToyBase.OnRemoved += Handlers.Internal.AdminToyList.OnRemovedAdminToys; ServerSpecificSettingsSync.ServerOnSettingValueReceived += SettingBase.OnSettingUpdated; - EventManager.RegisterEvents(this); + ServerConsole.ReloadServerName(); } /// @@ -118,8 +120,6 @@ public override void OnDisabled() ItemPickupBase.OnPickupDestroyed -= Handlers.Internal.PickupEvent.OnRemovedPickup; ServerSpecificSettingsSync.ServerOnSettingValueReceived -= SettingBase.OnSettingUpdated; - - EventManager.UnregisterEvents(this); } /// diff --git a/EXILED/Exiled.Events/Exiled.Events.csproj b/EXILED/Exiled.Events/Exiled.Events.csproj index aa7f9ff4bf..620625057d 100644 --- a/EXILED/Exiled.Events/Exiled.Events.csproj +++ b/EXILED/Exiled.Events/Exiled.Events.csproj @@ -26,10 +26,10 @@ + - diff --git a/EXILED/Exiled.Events/Handlers/Internal/AdminToyList.cs b/EXILED/Exiled.Events/Handlers/Internal/AdminToyList.cs new file mode 100644 index 0000000000..2d4ebf971e --- /dev/null +++ b/EXILED/Exiled.Events/Handlers/Internal/AdminToyList.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Handlers.Internal +{ + /// + /// Handles adding and removing from . + /// + internal static class AdminToyList + { + /// + /// Called after a ragdoll is spawned. Hooked to . + /// + /// The spawned ragdoll. + public static void OnAddedAdminToys(AdminToys.AdminToyBase adminToy) => API.Features.Toys.AdminToy.Get(adminToy); + + /// + /// Called before a ragdoll is destroyed. Hooked to . + /// + /// The destroyed ragdoll. + public static void OnRemovedAdminToys(AdminToys.AdminToyBase adminToy) => API.Features.Toys.AdminToy.BaseToAdminToy.Remove(adminToy); + } +} diff --git a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs index a260cf4957..8115dc4e65 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs @@ -33,21 +33,38 @@ public static void OnClientStarted() foreach (KeyValuePair prefab in NetworkClient.prefabs) { - if(!prefabs.ContainsKey(prefab.Key)) + if (!prefabs.ContainsKey(prefab.Key)) prefabs.Add(prefab.Key, prefab.Value); } foreach (NetworkIdentity ragdollPrefab in RagdollManager.AllRagdollPrefabs) { - if(!prefabs.ContainsKey(ragdollPrefab.assetId)) + if (!prefabs.ContainsKey(ragdollPrefab.assetId)) prefabs.Add(ragdollPrefab.assetId, ragdollPrefab.gameObject); } - foreach (PrefabType prefabType in EnumUtils.Values) + for (int i = 0; i < EnumUtils.Values.Length; i++) { + PrefabType prefabType = EnumUtils.Values[i]; PrefabAttribute attribute = prefabType.GetPrefabAttribute(); - PrefabHelper.Prefabs.Add(prefabType, prefabs.FirstOrDefault(prefab => prefab.Key == attribute.AssetId || prefab.Value.name.Contains(attribute.Name)).Value); + if (prefabs.TryGetValue(attribute.AssetId, out GameObject gameObject)) + { + PrefabHelper.Prefabs.Add(prefabType, gameObject); + prefabs.Remove(attribute.AssetId); + continue; + } + + KeyValuePair? value = prefabs.FirstOrDefault(x => x.Value.name == attribute.Name); + if (value.HasValue) + { + PrefabHelper.Prefabs.Add(prefabType, gameObject); + prefabs.Remove(value.Value.Key); + continue; + } } + + foreach (KeyValuePair missing in prefabs) + Log.Warn($"Missing prefab in {nameof(PrefabType)}: {missing.Value.name} ({missing.Key})"); } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs b/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs index 63bb70267e..55b9d0738b 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/SceneUnloaded.cs @@ -35,7 +35,6 @@ public static void OnSceneUnloaded(Scene _) { Player.UserIdsCache.Clear(); Player.Dictionary.Clear(); - AdminToy.BaseToAdminToy.Clear(); } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e272c42f3c..e35df7b044 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -17,10 +17,6 @@ namespace Exiled.Events.Handlers using Exiled.Events.Features; - using PluginAPI.Core.Attributes; - using PluginAPI.Enums; - using PluginAPI.Events; - /// /// Player related events. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs b/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs index 1bdacfd8f2..0cb378ac02 100644 --- a/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs +++ b/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs @@ -22,7 +22,7 @@ namespace Exiled.Events.Patches.Events.Cassie using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Cassie), nameof(Cassie.SendingCassieMessage))] @@ -35,35 +35,62 @@ private static IEnumerable Transpiler(IEnumerable and adds implementation. /// Adds the event. /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.KeycardInteracting))] [HarmonyPatch(typeof(BaseKeycardPickup), nameof(BaseKeycardPickup.ProcessCollision))] internal static class KeycardInteracting { @@ -43,113 +40,65 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); LocalBuilder isUnlocked = generator.DeclareLocal(typeof(bool)); - LocalBuilder notEmptyPermissions = generator.DeclareLocal(typeof(bool)); - LocalBuilder havePermissions = generator.DeclareLocal(typeof(bool)); + LocalBuilder hasPermission = generator.DeclareLocal(typeof(bool)); - Label skip = generator.DefineLabel(); Label ret = generator.DefineLabel(); int offset = 1; int index = newInstructions.FindIndex(i => i.LoadsField(Field(typeof(DoorVariant), nameof(DoorVariant.ActiveLocks)))) + offset; + Label continueLabel = (Label)newInstructions[index].operand; + newInstructions.RemoveAt(index); newInstructions.InsertRange( index, - new[] + new CodeInstruction[] { // check and write door lock state (isUnlocked) new(OpCodes.Ldc_I4_0), new(OpCodes.Ceq), - new CodeInstruction(OpCodes.Stloc_S, isUnlocked.LocalIndex), + new(OpCodes.Stloc_S, isUnlocked.LocalIndex), + new(OpCodes.Br, continueLabel), }); - index = newInstructions.FindIndex(i => i.LoadsField(Field(typeof(DoorPermissions), nameof(DoorPermissions.RequiredPermissions)))) + offset; + offset = 1; + index = newInstructions.FindIndex(i => i.Calls(Method( + typeof(DoorPermissionsPolicyExtensions), + nameof(DoorPermissionsPolicyExtensions.CheckPermissions), + new[] { typeof(IDoorPermissionRequester), typeof(IDoorPermissionProvider), typeof(PermissionUsed).MakeByRefType() }))) + offset; newInstructions.InsertRange( index, - new[] - { - // checking empty permissions - new(OpCodes.Ldc_I4_0), - new(OpCodes.Cgt), - - new(OpCodes.Stloc_S, notEmptyPermissions.LocalIndex), - new(OpCodes.Br_S, skip), - - // save original return - new CodeInstruction(OpCodes.Ret).MoveLabelsFrom(newInstructions[index + 1]), - new CodeInstruction(OpCodes.Nop).WithLabels(skip), - }); - - // 6 new instructions - offset = 6; - index += offset; + new CodeInstruction[] + { + // hasPermission + new(OpCodes.Stloc_S, hasPermission.LocalIndex), - newInstructions.RemoveRange(index, 14); + // pickup + new(OpCodes.Ldarg_0), - newInstructions.InsertRange( - index, - new[] - { - // override permissions check, to implement KeycardPickup::Permissions - new(OpCodes.Ldarg_0), - new(OpCodes.Ldloc_1), - new CodeInstruction(OpCodes.Call, Method(typeof(KeycardInteracting), nameof(KeycardInteracting.CheckPermissions))), - new CodeInstruction(OpCodes.Stloc_S, havePermissions.LocalIndex), - }); + // PreviousOwner.Hub + new(OpCodes.Ldarg_0), + new(OpCodes.Ldflda, Field(typeof(BaseKeycardPickup), nameof(BaseKeycardPickup.PreviousOwner))), + new(OpCodes.Ldfld, Field(typeof(Footprint), nameof(Footprint.Hub))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - // 4 new instructions - offset = 4; - index += offset; + // door + new(OpCodes.Ldloc_1), - newInstructions.RemoveRange(index, 2); + // isAllowed = isUnlocked && hasPermission + new(OpCodes.Ldloc_S, isUnlocked.LocalIndex), + new(OpCodes.Ldloc_S, hasPermission.LocalIndex), + new(OpCodes.And), - offset = -5; - index = newInstructions.FindIndex(i => i.Calls(PropertySetter(typeof(DoorVariant), nameof(DoorVariant.NetworkTargetState)))) + offset; + // ev = new KeycardInteractingEventArgs(pickup, player, door, isAllowed) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(KeycardInteractingEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnKeycardInteracting))), - newInstructions.InsertRange( - index, - new[] - { - // pickup - new(OpCodes.Ldarg_0), - - // PreviousOwner.Hub - new CodeInstruction(OpCodes.Ldarg_0), - new(OpCodes.Ldflda, Field(typeof(BaseKeycardPickup), nameof(BaseKeycardPickup.PreviousOwner))), - new(OpCodes.Ldfld, Field(typeof(Footprint), nameof(Footprint.Hub))), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - - // door - new(OpCodes.Ldloc_1), - - // allowed calculate - new(OpCodes.Ldloc_S, isUnlocked), - - new(OpCodes.Ldloc_S, havePermissions), - - new(OpCodes.Ldloc_S, notEmptyPermissions), - new(OpCodes.Call, PropertyGetter(typeof(Events), nameof(Events.Instance))), - new(OpCodes.Callvirt, PropertyGetter(typeof(Events), nameof(Events.Config))), - new(OpCodes.Callvirt, PropertyGetter(typeof(Config), nameof(Config.CanKeycardThrowAffectDoors))), - new(OpCodes.Or), - - new(OpCodes.And), - new(OpCodes.And), - - // ThrowKeycardInteractingEventArgs ev = new(pickup, player, door, isAllowed); - // - // Item.OnThrowKeycardInteracting(ev); - // - // if (!ev.IsAllowed) - // return; - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(KeycardInteractingEventArgs))[0]), - new(OpCodes.Dup), - new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnKeycardInteracting))), - new(OpCodes.Callvirt, PropertyGetter(typeof(KeycardInteractingEventArgs), nameof(KeycardInteractingEventArgs.IsAllowed))), - new(OpCodes.Brfalse_S, ret), - }); + new(OpCodes.Callvirt, PropertyGetter(typeof(KeycardInteractingEventArgs), nameof(KeycardInteractingEventArgs.IsAllowed))), + }); newInstructions.InsertRange( newInstructions.Count - 1, @@ -167,26 +116,5 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - - private static bool CheckPermissions(BaseKeycardPickup keycard, DoorVariant door) - { - DoorPermissions permissions = door.RequiredPermissions; - if (permissions.RequiredPermissions == KeycardPermissions.None) - { - return true; - } - - if (Pickup.Get(keycard) is KeycardPickup keycardPickup) - { - if (!permissions.RequireAll) - { - return ((KeycardPermissions)keycardPickup.Permissions & permissions.RequiredPermissions) != 0; - } - - return ((KeycardPermissions)keycardPickup.Permissions & permissions.RequiredPermissions) == permissions.RequiredPermissions; - } - - return InventorySystem.InventoryItemLoader.AvailableItems.TryGetValue(keycard.Info.ItemId, out ItemBase itemBase) && permissions.CheckPermissions(itemBase, null); - } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs index 1bf2c9c02d..0d53ebeefb 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs @@ -42,7 +42,7 @@ private static IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Ldloca_S); + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Pop) + 1; newInstructions.InsertRange(index, new[] { diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs index f9663ecc7e..4d169bf050 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs @@ -55,8 +55,8 @@ private static IEnumerable Transpiler(IEnumerable x.characterClassManager.CurRole.team == Team.SCP && x.characterClassManager.CurClass != RoleTypeId.Scp0492); - new(OpCodes.Ldloc_3), + // scpsLeft + new(OpCodes.Ldloc_2), // string[] unitInformation = unitNameClear.Split('-'); new(OpCodes.Ldloc_1), @@ -111,7 +111,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Ldloc_0) + offset; + int offset = -3; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Newobj && (ConstructorInfo)i.operand == GetDeclaredConstructors(typeof(LabApi.Events.Arguments.ServerEvents.CassieQueuingScpTerminationEventArgs))[0]) + offset; newInstructions.InsertRange( index, @@ -53,10 +54,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); int offset = 1; - int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Stloc_3) + offset; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Stloc_S && i.operand is LocalBuilder { LocalIndex: 5 }) + offset; - Label returnLabel = generator.DefineLabel(); + Label continueLabel = generator.DefineLabel(); LocalBuilder ev = generator.DeclareLocal(typeof(ExplodingGrenadeEventArgs)); - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // attacker; - new(OpCodes.Ldarg_0), - - // position - new(OpCodes.Ldarg_1), - - // grenade - new(OpCodes.Ldarg_2), - - // Collider[] - new(OpCodes.Ldloc_3), - - // explosionType - new(OpCodes.Ldarg_3), - - // ExplodingGrenadeEventArgs ev = new(player, position, grenade, colliders, ExplosionType); - new(OpCodes.Newobj, DeclaredConstructor(typeof(ExplodingGrenadeEventArgs), new[] { typeof(Footprint), typeof(Vector3), typeof(ExplosionGrenade), typeof(Collider[]), typeof(ExplosionType) })), - new(OpCodes.Dup), - new(OpCodes.Dup), - new(OpCodes.Stloc, ev.LocalIndex), - - // Map.OnExplodingGrenade(ev); - new(OpCodes.Call, Method(typeof(Handlers.Map), nameof(Handlers.Map.OnExplodingGrenade))), - - // if (!ev.IsAllowed) - // return; - new(OpCodes.Callvirt, PropertyGetter(typeof(ExplodingGrenadeEventArgs), nameof(ExplodingGrenadeEventArgs.IsAllowed))), - new(OpCodes.Brfalse, returnLabel), - - // colliders = TrimColliders(ev, colliders) - new(OpCodes.Ldloc, ev.LocalIndex), - new(OpCodes.Ldloc_3), - new(OpCodes.Call, Method(typeof(ExplodingFragGrenade), nameof(TrimColliders))), - new(OpCodes.Stloc_3), - }); - - newInstructions[newInstructions.Count - 1].labels.Add(returnLabel); + newInstructions.InsertRange(index, new CodeInstruction[] + { + // attacker; + new(OpCodes.Ldarg_0), + + // position + new(OpCodes.Ldarg_1), + + // grenade + new(OpCodes.Ldarg_2), + + // Collider[] + new(OpCodes.Ldloc_S, 5), + + // explosionType + new(OpCodes.Ldarg_3), + + // ExplodingGrenadeEventArgs ev = new(Footprint, position, grenade, colliders, ExplosionType); + new(OpCodes.Newobj, DeclaredConstructor(typeof(ExplodingGrenadeEventArgs), new[] { typeof(Footprint), typeof(Vector3), typeof(ExplosionGrenade), typeof(Collider[]), typeof(ExplosionType) })), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc, ev.LocalIndex), + + // Map.OnExplodingGrenade(ev); + new(OpCodes.Call, Method(typeof(Handlers.Map), nameof(Handlers.Map.OnExplodingGrenade))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(ExplodingGrenadeEventArgs), nameof(ExplodingGrenadeEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, continueLabel), + + // HashSetPool.Shared.Return(hashSet); + new(OpCodes.Ldsfld, Field(typeof(NorthwoodLib.Pools.HashSetPool), nameof(NorthwoodLib.Pools.HashSetPool.Shared))), + new(OpCodes.Ldloc_2), + new(OpCodes.Callvirt, Method(typeof(NorthwoodLib.Pools.HashSetPool), nameof(NorthwoodLib.Pools.HashSetPool.Return))), + + // HashSetPool.Shared.Return(hashSet2); + new(OpCodes.Ldsfld, Field(typeof(NorthwoodLib.Pools.HashSetPool), nameof(NorthwoodLib.Pools.HashSetPool.Shared))), + new(OpCodes.Ldloc_3), + new(OpCodes.Callvirt, Method(typeof(NorthwoodLib.Pools.HashSetPool), nameof(NorthwoodLib.Pools.HashSetPool.Return))), + + // return; + new(OpCodes.Ret), + + // colliders = TrimColliders(ev, colliders) + new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex).WithLabels(continueLabel), + new(OpCodes.Ldloc_S, 5), + new(OpCodes.Call, Method(typeof(ExplodingFragGrenade), nameof(TrimColliders))), + new(OpCodes.Stloc_S, 5), + }); for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; diff --git a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs index 0293950826..df768a3048 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/SpawningItem.cs @@ -24,11 +24,11 @@ namespace Exiled.Events.Patches.Events.Map using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Map), nameof(Map.SpawningItem))] - [HarmonyPatch(typeof(ItemDistributor), nameof(ItemDistributor.CreatePickup))] + [HarmonyPatch(typeof(ItemDistributor), nameof(ItemDistributor.ServerRegisterPickup))] internal static class SpawningItem { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/EXILED/Exiled.Events/Patches/Events/Map/SpawningRoomConnector.cs b/EXILED/Exiled.Events/Patches/Events/Map/SpawningRoomConnector.cs index 7b11934f9d..b379bdec1a 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/SpawningRoomConnector.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/SpawningRoomConnector.cs @@ -88,14 +88,14 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - int offset = 0; - int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(RoomConnectorSpawnpointBase), nameof(RoomConnectorSpawnpointBase.SetupAllRoomConnectors)))) + offset; + int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(RoomConnectorSpawnpointBase), nameof(RoomConnectorSpawnpointBase.SetupAllRoomConnectors)))); + List - [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.InteractingDoor))] + [EventPatch(typeof(Handlers.Player), nameof(Player.InteractingDoor))] [HarmonyPatch(typeof(DoorVariant), nameof(DoorVariant.ServerInteract), typeof(ReferenceHub), typeof(byte))] internal static class InteractingDoor { @@ -33,92 +33,166 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - LocalBuilder ev = generator.DeclareLocal(typeof(InteractingDoorEventArgs)); + LocalBuilder labEvent = generator.DeclareLocal(typeof(PlayerInteractedDoorEventArgs)); + + Label exiledEvContinue = generator.DefineLabel(); + Label labEvContinue = generator.DefineLabel(); + + int offset = -3; + int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(DoorVariant), nameof(DoorVariant.AllowInteracting)))) + offset; - List [Description("The config files distribution type (Default, Separated)")] - public ConfigType ConfigType { get; set; } = ConfigType.Default; + public ConfigType ConfigType { get; set; } = ConfigType.Separated; /// /// Gets or sets the quotes wrapper type. diff --git a/EXILED/Exiled.Loader/ConfigManager.cs b/EXILED/Exiled.Loader/ConfigManager.cs index 3a59afedf0..e925517be9 100644 --- a/EXILED/Exiled.Loader/ConfigManager.cs +++ b/EXILED/Exiled.Loader/ConfigManager.cs @@ -273,7 +273,7 @@ public static void ReloadRemoteAdmin() ServerStatic.SharedGroupsConfig = GameCore.ConfigSharing.Paths[4] is null ? null : new YamlConfig(GameCore.ConfigSharing.Paths[4] + "shared_groups.txt"); ServerStatic.SharedGroupsMembersConfig = GameCore.ConfigSharing.Paths[5] is null ? null : new YamlConfig(GameCore.ConfigSharing.Paths[5] + "shared_groups_members.txt"); ServerStatic.PermissionsHandler = new PermissionsHandler(ref ServerStatic.RolesConfig, ref ServerStatic.SharedGroupsConfig, ref ServerStatic.SharedGroupsMembersConfig); - ServerStatic.GetPermissionsHandler().RefreshPermissions(); + ServerStatic.PermissionsHandler.RefreshPermissions(); foreach (Player player in Player.List) { diff --git a/EXILED/Exiled.Loader/Exiled.Loader.csproj b/EXILED/Exiled.Loader/Exiled.Loader.csproj index fc19e5aea6..55b587ef44 100644 --- a/EXILED/Exiled.Loader/Exiled.Loader.csproj +++ b/EXILED/Exiled.Loader/Exiled.Loader.csproj @@ -25,8 +25,8 @@ + - diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs index 2c3ce68de9..7c2194d09f 100644 --- a/EXILED/Exiled.Loader/Loader.cs +++ b/EXILED/Exiled.Loader/Loader.cs @@ -46,7 +46,7 @@ public Loader() Log.Warn("You are running a public beta build. It is not compatible with another version of the game."); #endif - Log.SendRaw($"Exiled.API - Version {PluginAPI.Loader.AssemblyLoader.Dependencies.FirstOrDefault(x => x.GetName().Name == "Exiled.API").GetCustomAttribute().InformationalVersion}", ConsoleColor.DarkRed); + Log.SendRaw($"Exiled.API - Version {LabApi.Loader.PluginLoader.Dependencies.FirstOrDefault(x => x.GetName().Name == "Exiled.API").GetCustomAttribute().InformationalVersion}", ConsoleColor.DarkRed); Log.SendRaw($"{Assembly.GetExecutingAssembly().GetName().Name} - Version {Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion}", ConsoleColor.DarkRed); if (MultiAdminFeatures.MultiAdminUsed) diff --git a/EXILED/Exiled.Loader/LoaderPlugin.cs b/EXILED/Exiled.Loader/LoaderPlugin.cs index 349da02566..b2f5215ccd 100644 --- a/EXILED/Exiled.Loader/LoaderPlugin.cs +++ b/EXILED/Exiled.Loader/LoaderPlugin.cs @@ -11,33 +11,69 @@ namespace Exiled.Loader using System.IO; using System.Reflection; - using MEC; + using LabApi.Loader.Features.Plugins; + using LabApi.Loader.Features.Plugins.Enums; - using PluginAPI.Core.Attributes; + using MEC; using Log = API.Features.Log; using Paths = API.Features.Paths; /// - /// The Northwood PluginAPI Plugin class for the EXILED Loader. + /// The Northwood LabAPI Plugin class for the EXILED Loader. /// - public class LoaderPlugin + public class LoaderPlugin : Plugin { #pragma warning disable SA1401 /// /// The config for the EXILED Loader. /// - [PluginConfig] - public static Config Config; + public static new Config Config; + + /// + /// The config for the EXILED Loader. + /// + public static LoaderPlugin Instance; #pragma warning restore SA1401 /// - /// Called by PluginAPI when the plugin is enabled. + /// Gets the Name of the EXILED Loader. + /// + public override string Name => "Exiled Loader"; + + /// + /// Gets the Description of the EXILED Loader. + /// + public override string Description => "Loads the EXILED Plugin Framework."; + + /// + /// Gets the Author of the EXILED Loader. + /// + public override string Author => "ExMod-Team"; + + /// + /// Gets the RequiredApiVersion of the EXILED Loader. /// - [PluginEntryPoint("Exiled Loader", null, "Loads the EXILED Plugin Framework.", "ExMod-Team")] - [PluginPriority(byte.MinValue)] - public void Enable() + public override Version RequiredApiVersion { get; } = Assembly.GetAssembly(typeof(LabApi.Loader.PluginLoader)).GetReferencedAssemblies()[0].Version; // TODO Not finish + + /// + /// Gets the Exiled Version. + /// + public override Version Version => Loader.Version; + + /// + /// Gets the Exiled Priority load. + /// + public override LoadPriority Priority { get; } = (LoadPriority)byte.MaxValue; + + /// + /// Called by LabAPI when the plugin is enabled. + /// + public override void Enable() { + Instance = this; + Config = base.Config; + if (Config == null) { Log.Error("Detected null config, EXILED will not be loaded."); @@ -63,5 +99,13 @@ public void Enable() Timing.RunCoroutine(new Loader().Run()); } + + /// + /// Called by LabAPI when the plugin is Disable. + /// + public override void Disable() + { + // Plugin will not be disable + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 448adfd6c2..a90f0589b0 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -69,7 +69,7 @@ where name.StartsWith("Exiled.", StringComparison.OrdinalIgnoreCase) && name != Assembly.GetExecutingAssembly().GetName().Name select new ExiledLib(a); - private string Folder => File.Exists($"{PluginAPI.Helpers.Paths.GlobalPlugins.Plugins}/Exiled.Loader.dll") ? "global" : Server.Port.ToString(); + private string Folder => File.Exists(Path.Combine(LabApi.Loader.Features.Paths.PathManager.Plugins.FullName, "global", "Exiled.Loader.dll")) ? "global" : Server.Port.ToString(); private string InstallerName { @@ -116,7 +116,7 @@ internal void CheckUpdate() try { using HttpClient client = CreateHttpClient(); - if (Busy = FindUpdate(client, !PluginAPI.Loader.AssemblyLoader.Dependencies.Exists(x => x.GetName().Name == "Exiled.API"), out NewVersion newVersion)) + if (Busy = FindUpdate(client, !LabApi.Loader.PluginLoader.Dependencies.Any(x => x.GetName().Name == "Exiled.API"), out NewVersion newVersion)) Update(client, newVersion); } catch (Exception e) @@ -136,7 +136,7 @@ private HttpClient CreateHttpClient() Timeout = TimeSpan.FromSeconds(480), }; - client.DefaultRequestHeaders.Add("User-Agent", $"Exiled.Loader (https://github.com/ExMod-Team/EXILED, {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)})"); + client.DefaultRequestHeaders.Add("User-Agent", $"Exiled.Loader (https://github.com/ExSLMod-Team/EXILED, {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)})"); return client; } diff --git a/EXILED/Exiled.Permissions/Exiled.Permissions.csproj b/EXILED/Exiled.Permissions/Exiled.Permissions.csproj index 3c130764f8..7057fc5d5d 100644 --- a/EXILED/Exiled.Permissions/Exiled.Permissions.csproj +++ b/EXILED/Exiled.Permissions/Exiled.Permissions.csproj @@ -23,10 +23,10 @@ + - diff --git a/EXILED/Exiled.Permissions/Extensions/Permissions.cs b/EXILED/Exiled.Permissions/Extensions/Permissions.cs index b03271b4be..56823e3feb 100644 --- a/EXILED/Exiled.Permissions/Extensions/Permissions.cs +++ b/EXILED/Exiled.Permissions/Extensions/Permissions.cs @@ -105,7 +105,7 @@ public static void Reload() { try { - if (string.Equals(group.Key, "user", StringComparison.OrdinalIgnoreCase) || ServerStatic.PermissionsHandler._groups.ContainsKey(group.Key)) + if (string.Equals(group.Key, "user", StringComparison.OrdinalIgnoreCase) || ServerStatic.PermissionsHandler.Groups.ContainsKey(group.Key)) { deserializedPerms.Add(group.Key, Deserializer.Deserialize(Serializer.Serialize(group.Value))); } @@ -207,7 +207,7 @@ public static bool CheckPermission(this Player player, string permission) Log.Debug($"UserID: {player.UserId} | PlayerId: {player.Id}"); Log.Debug($"Permission string: {permission}"); - string plyGroupKey = player.Group is not null ? ServerStatic.GetPermissionsHandler()._groups.FirstOrDefault(g => g.Value.EqualsTo(player.Group)).Key : null; + string plyGroupKey = player.Group is not null ? ServerStatic.PermissionsHandler.Groups.FirstOrDefault(g => g.Value.EqualsTo(player.Group)).Key : null; Log.Debug($"GroupKey: {plyGroupKey ?? "(null)"}"); if (plyGroupKey is null || !Groups.TryGetValue(plyGroupKey, out Group group)) diff --git a/EXILED/Exiled/Exiled.nuspec b/EXILED/Exiled/Exiled.nuspec index 388d8ae0e9..cf95b0fbc8 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/ExMod-Team/EXILED/blob/master/LICENSE - https://github.com/ExMod-Team/EXILED + + https://github.com/ExSLMod-Team/EXILED/blob/master/LICENSE + https://github.com/ExSLMod-Team/EXILED images\Exiled_Icon.png Plugin framework for SCP: Secret Laboratory. @@ -26,7 +26,7 @@ - + diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 66dd2d881e..31ee11803d 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.0.0.2) +Last Update (14.1.0.0) ### Index @@ -61,8 +61,7 @@ Last Update (14.0.0.2) - [ChallengeState](#challengestate) - [ChallengeType](#challengetype) - [ChamberState](#chamberstate) -- [CheckpointErrorType](#checkpointerrortype) -- [CheckpointSequenceStage](#checkpointsequencestage) +- [ChaosMsgType](#chaosmsgtype) - [ClientFlags](#clientflags) - [ClientInstanceMode](#clientinstancemode) - [ClientReceivedContentType](#clientreceivedcontenttype) @@ -72,6 +71,7 @@ Last Update (14.0.0.2) - [CmdTeleportData](#cmdteleportdata) - [CoffeeTranslation](#coffeetranslation) - [CollectionDeserializeToBehaviour](#collectiondeserializetobehaviour) +- [ColliderShape](#collidershape) - [CollisionsDisablingReasons](#collisionsdisablingreasons) - [ColorMode](#colormode) - [CommandOperationMode](#commandoperationmode) @@ -89,6 +89,7 @@ Last Update (14.0.0.2) - [DecalPoolType](#decalpooltype) - [DecontaminationStatus](#decontaminationstatus) - [DeliveryMethod](#deliverymethod) +- [DetectionStatus](#detectionstatus) - [DiodeType](#diodetype) - [DisconnectReason](#disconnectreason) - [DisconnectResult](#disconnectresult) @@ -101,6 +102,7 @@ Last Update (14.0.0.2) - [DoorDamageType](#doordamagetype) - [DoorLockMode](#doorlockmode) - [DoorLockReason](#doorlockreason) +- [DoorPermissionFlags](#doorpermissionflags) - [DropdownEntryType](#dropdownentrytype) - [DtoaMode](#dtoamode) - [EffectClassification](#effectclassification) @@ -133,6 +135,8 @@ Last Update (14.0.0.2) - [FoldoutMode](#foldoutmode) - [FootprintsTranslation](#footprintstranslation) - [FootstepLoudness](#footsteploudness) +- [ForceCondition](#forcecondition) +- [ForceResult](#forceresult) - [FpcViewMode](#fpcviewmode) - [FreezingMode](#freezingmode) - [FriendlyFireAction](#friendlyfireaction) @@ -171,7 +175,8 @@ Last Update (14.0.0.2) - [JailbirdMessageType](#jailbirdmessagetype) - [JailbirdWearState](#jailbirdwearstate) - [JsonToken](#jsontoken) -- [KeycardPermissions](#keycardpermissions) +- [KeycardLabelTranslation](#keycardlabeltranslation) +- [KeyCodeTranslations](#keycodetranslations) - [LcdElementType](#lcdelementtype) - [LeadingTeam](#leadingteam) - [LegacyInterfaces](#legacyinterfaces) @@ -196,6 +201,7 @@ Last Update (14.0.0.2) - [Mode](#mode) - [ModifierMode](#modifiermode) - [Modules](#modules) +- [MsgType](#msgtype) - [NatAddressType](#nataddresstype) - [NetLogLevel](#netloglevel) - [NetworkProtocolType](#networkprotocoltype) @@ -231,12 +237,14 @@ Last Update (14.0.0.2) - [RadioCommand](#radiocommand) - [RadioRangeLevel](#radiorangelevel) - [RejectionReason](#rejectionreason) +- [RejectionReason](#rejectionreason) - [ReloaderMessageHeader](#reloadermessageheader) - [RemoteAdminResponseFlags](#remoteadminresponseflags) - [RemovalMode](#removalmode) - [ReproProjectAssetType](#reproprojectassettype) - [RequestType](#requesttype) - [RespawnSetting](#respawnsetting) +- [RespawnTooltipTranslation](#respawntooltiptranslation) - [ResurrectError](#resurrecterror) - [RoleChangeReason](#rolechangereason) - [RoleSpawnFlags](#rolespawnflags) @@ -257,6 +265,7 @@ Last Update (14.0.0.2) - [RpcType](#rpctype) - [RpcType](#rpctype) - [RpcType](#rpctype) +- [RpcType](#rpctype) - [ScanSequenceStep](#scansequencestep) - [Scp0492SoundId](#scp0492soundid) - [Scp079HudTranslation](#scp079hudtranslation) @@ -264,6 +273,8 @@ Last Update (14.0.0.2) - [Scp096HitResult](#scp096hitresult) - [Scp096HudTranslation](#scp096hudtranslation) - [Scp096RageState](#scp096ragestate) +- [Scp127Tier](#scp127tier) +- [Scp127VoiceLinesTranslation](#scp127voicelinestranslation) - [Scp1344Status](#scp1344status) - [Scp173SoundId](#scp173soundid) - [Scp244State](#scp244state) @@ -275,8 +286,10 @@ Last Update (14.0.0.2) - [Scp939DamageType](#scp939damagetype) - [Scp939HudTranslation](#scp939hudtranslation) - [Scp939LungeState](#scp939lungestate) +- [ScpSetting](#scpsetting) - [SecurityLevel](#securitylevel) - [SensitivitySetting](#sensitivitysetting) +- [SequenceState](#sequencestate) - [ServerLogType](#serverlogtype) - [ServerOperativeSystem](#serveroperativesystem) - [ServerRateLimit](#serverratelimit) @@ -290,6 +303,7 @@ Last Update (14.0.0.2) - [SpectatorSpawnReason](#spectatorspawnreason) - [State](#state) - [States](#states) +- [StatMessageType](#statmessagetype) - [StatusType](#statustype) - [SteamLobbyPrivacy](#steamlobbyprivacy) - [StorageLocation](#storagelocation) @@ -318,6 +332,7 @@ Last Update (14.0.0.2) - [VersionType](#versiontype) - [VoiceChatChannel](#voicechatchannel) - [VoiceChatSupportMode](#voicechatsupportmode) +- [VoiceLinePriority](#voicelinepriority) - [VoiceLinesName](#voicelinesname) - [VolumeSliderSetting](#volumeslidersetting) - [WarheadScenarioType](#warheadscenariotype) @@ -382,6 +397,7 @@ Last Update (14.0.0.2) [48] = UndeadSpaceProgram [49] = ArizonaRanger [50] = Matador + [51] = ToothAndNail ``` @@ -986,27 +1002,15 @@ Last Update (14.0.0.2) -### CheckpointErrorType +### ChaosMsgType -
Interactables.Interobjects.CheckpointDoor+CheckpointErrorType +
InventorySystem.Items.Keycards.ChaosKeycardItem+ChaosMsgType ``` - [0] = Denied - [1] = LockedDown - [2] = Destroyed -``` - -
- -### CheckpointSequenceStage - -
Interactables.Interobjects.CheckpointDoor+CheckpointSequenceStage - -``` - [0] = Idle - [1] = Granted - [2] = Open - [3] = Closing + [0] = SnakeMsgSync + [1] = NewConnectionFullSync + [2] = MovementSwitch + [3] = UseDetails ```
@@ -1161,6 +1165,18 @@ Last Update (14.0.0.2)
+### ColliderShape + +
AdminToys.InvisibleInteractableToy+ColliderShape + +``` + [0] = Box + [1] = Sphere + [2] = Capsule +``` + +
+ ### CollisionsDisablingReasons
Interactables.Interobjects.DoorUtils.DoorVariant+CollisionsDisablingReasons @@ -1413,6 +1429,18 @@ Last Update (14.0.0.2)
+### DetectionStatus + +
InventorySystem.Items.Firearms.Modules.Scp127.Scp127CassieBasedVoiceTriggerBase+DetectionStatus + +``` + [0] = Idle + [1] = WaitingForTrigger + [2] = AnnouncementStartedPlaying +``` + +
+ ### DiodeType
AlphaWarheadNukesitePanel+DiodeType @@ -1585,6 +1613,28 @@ Last Update (14.0.0.2)
+### DoorPermissionFlags + +
Interactables.Interobjects.DoorUtils.DoorPermissionFlags + +``` + [0] = None + [1] = Checkpoints + [2] = ExitGates + [4] = Intercom + [8] = AlphaWarhead + [16] = ContainmentLevelOne + [32] = ContainmentLevelTwo + [64] = ContainmentLevelThree + [128] = ArmoryLevelOne + [256] = ArmoryLevelTwo + [512] = ArmoryLevelThree + [1024] = ScpOverride + [65535] = All +``` + +
+ ### DropdownEntryType
UserSettings.ServerSpecific.SSDropdownSetting+DropdownEntryType @@ -1649,6 +1699,7 @@ Last Update (14.0.0.2) [6] = Nuke01 [7] = Scp049 [8] = Nuke02 + [9] = ServerRoom ```
@@ -1658,11 +1709,12 @@ Last Update (14.0.0.2)
Interactables.Interobjects.ElevatorChamber+ElevatorSequence ``` - [0] = DoorClosing - [1] = MovingAway - [2] = Arriving - [3] = DoorOpening - [4] = Ready + [0] = StartingSequence + [1] = DoorClosing + [2] = MovingAway + [3] = Arriving + [4] = DoorOpening + [5] = Ready ```
@@ -1747,6 +1799,7 @@ Last Update (14.0.0.2) [2] = CuffedClassD [3] = Scientist [4] = CuffedScientist + [5] = Custom ``` @@ -2022,6 +2075,7 @@ Last Update (14.0.0.2) [9] = HalloweenOutside [10] = ChristmasInside [11] = ChristmasOutside + [12] = PocketDimension ``` @@ -2071,6 +2125,29 @@ Last Update (14.0.0.2) +### ForceCondition + +
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.GlowInTheDarkSubcontroller+ForceCondition + +``` + [0] = NeverForce + [1] = ForceWhenFriendly + [2] = ForceWhenEnemy +``` + +
+ +### ForceResult + +
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.GlowInTheDarkSubcontroller+ForceResult + +``` + [0] = ForceDarkened + [1] = ForceNormal +``` + +
+ ### FpcViewMode
PlayerRoles.FirstPersonControl.FpcMotor+FpcViewMode @@ -2268,6 +2345,7 @@ Last Update (14.0.0.2) [15] = PackedLong [16] = PackedULong [17] = Scp330Hint + [18] = SSKeybind ```
@@ -2498,6 +2576,8 @@ Last Update (14.0.0.2) [24] = JailbirdChargeHint [25] = MicroHidReadyToDischarge [26] = MicroHidDamaged + [27] = Scp127OnEquip + [28] = SnakeHint ``` @@ -2658,6 +2738,13 @@ Last Update (14.0.0.2) [57] = Coal [58] = SpecialCoal [59] = SCP1507Tape + [60] = DebugRagdollMover + [61] = SurfaceAccessPass + [62] = GunSCP127 + [63] = KeycardCustomTaskForce + [64] = KeycardCustomSite02 + [65] = KeycardCustomManagement + [66] = KeycardCustomMetalCase [-1] = None ``` @@ -2717,23 +2804,48 @@ Last Update (14.0.0.2) -### KeycardPermissions +### KeycardLabelTranslation -
Interactables.Interobjects.DoorUtils.KeycardPermissions +
InventorySystem.Items.Keycards.TranslatedLabelDetail+KeycardLabelTranslation ``` - [0] = None - [1] = Checkpoints - [2] = ExitGates - [4] = Intercom - [8] = AlphaWarhead - [16] = ContainmentLevelOne - [32] = ContainmentLevelTwo - [64] = ContainmentLevelThree - [128] = ArmoryLevelOne - [256] = ArmoryLevelTwo - [512] = ArmoryLevelThree - [1024] = ScpOverride + [0] = Scientist + [1] = Janitor + [2] = ResearchSupervisor + [3] = ContEngineer + [4] = SecurityGuard + [5] = ZoneManager + [6] = FacilityManager + [7] = SurfaceAccessPassNormal + [8] = SurfaceAccessPassUsed +``` + +
+ +### KeyCodeTranslations + +
Hints.KeyCodeTranslations + +``` + [0] = ServerSettingNotFound + [1] = KeyNotAssigned + [2] = ArrowUp + [3] = ArrowDown + [4] = ArrowLeft + [5] = ArrowRight + [6] = LeftShift + [7] = RightShift + [8] = LeftControl + [9] = RightControl + [10] = LeftAlt + [11] = RightAlt + [12] = Tab + [13] = Space + [14] = Enter + [15] = MousePrimary + [16] = MouseSecondary + [17] = MouseMiddle + [18] = MouseN ```
@@ -2851,12 +2963,14 @@ Last Update (14.0.0.2)
MapGeneration.MapGenerationPhase ``` - [0] = ParentRoomRegistration - [1] = RelativePositioningWaypoints - [2] = ComplexDecorationsAndClutter - [3] = SimpleDecorations - [4] = CullingCaching - [5] = SpawnableStructures + [0] = RoomCoordsRegistrations + [1] = ParentRoomRegistration + [2] = RelativePositioningWaypoints + [3] = ComplexDecorationsAndClutter + [4] = SimpleDecorations + [5] = CullingCaching + [6] = SpawnableStructures + [7] = StaticBatching ```
@@ -2885,6 +2999,7 @@ Last Update (14.0.0.2) [3] = RpcFire [4] = RpcDryFire [5] = RpcNewPlayerSync + [6] = RpcRejectionReason ```
@@ -2923,9 +3038,10 @@ Last Update (14.0.0.2) [1] = RpcRequireReloadFalse [2] = RpcRequireReloadFullResync [3] = RpcStartFiring - [4] = RpcOnShot - [5] = CmdRequestStartFiring - [6] = CmdConfirmDischarge + [4] = RpcStopFiring + [5] = RpcOnShot + [6] = CmdRequestStartFiring + [7] = CmdConfirmDischarge ``` @@ -2987,6 +3103,7 @@ Last Update (14.0.0.2) [5] = RightClickToDrop [6] = InventoryToggle [7] = Scp079KeybindZoneSwitching + [8] = AutomaticSpectatorSwitch ``` @@ -3017,6 +3134,7 @@ Last Update (14.0.0.2) [2] = HeadBobbing [3] = FlashbangDarkMode [4] = ShowNeedles + [5] = Scp939VisionBlur ``` @@ -3031,6 +3149,7 @@ Last Update (14.0.0.2) [2] = Weapons [3] = VoiceChat [4] = NoDucking + [5] = Scp127VoiceViewmodel ``` @@ -3091,6 +3210,19 @@ Last Update (14.0.0.2) +### MsgType + +
InventorySystem.Items.Keycards.KeycardItem+MsgType + +``` + [0] = Custom + [1] = OnKeycardUsed + [2] = Inspect + [3] = NewPlayerFullResync +``` + +
+ ### NatAddressType
LiteNetLib.NatAddressType @@ -3657,6 +3789,19 @@ Last Update (14.0.0.2)
+### RejectionReason + +
InventorySystem.Items.Firearms.Modules.AutomaticActionModule+RejectionReason + +``` + [1] = TimedOut + [2] = ModuleBusy + [3] = NotCocked + [4] = BoltLocked +``` + +
+ ### ReloaderMessageHeader
InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase+ReloaderMessageHeader @@ -3730,6 +3875,18 @@ Last Update (14.0.0.2)
+### RespawnTooltipTranslation + +
Respawning.Graphics.RespawnTooltipTranslation + +``` + [0] = TimerBarTranslation + [1] = InfluenceTranslation + [2] = RespawnsTokensTranslation +``` + +
+ ### ResurrectError
PlayerRoles.PlayableScps.Scp049.Scp049ResurrectAbility+ResurrectError @@ -3859,6 +4016,7 @@ Last Update (14.0.0.2) [35] = Outside [36] = Pocket [37] = HczTestroom + [38] = Hcz127 ```
@@ -4040,6 +4198,17 @@ Last Update (14.0.0.2) ### RpcType +
InventorySystem.Items.Firearms.Modules.Scp127.Scp127VoiceLineManagerModule+RpcType + +``` + [0] = OwnerRegistered + [1] = PlayLine +``` + +
+ +### RpcType +
InventorySystem.Items.Firearms.Attachments.FlashlightAttachment+RpcType ``` @@ -4234,6 +4403,181 @@ Last Update (14.0.0.2)
+### Scp127Tier + +
InventorySystem.Items.Firearms.Modules.Scp127.Scp127Tier + +``` + [0] = Tier1 + [1] = Tier2 + [2] = Tier3 +``` + +
+ +### Scp127VoiceLinesTranslation + +
InventorySystem.Items.Firearms.Modules.Scp127.Scp127VoiceLinesTranslation + +``` + [0] = ChamberingDammitThatHurts + [1] = ChamberingFeelThatTmrw + [2] = ChamberingGrunt1 + [3] = ChamberingGrunt2 + [4] = ChamberingGrunt3 + [5] = ChamberingGrunt4 + [6] = ChamberingHateThatPart + [7] = ChamberingNopeNeverGettinBetter + [8] = ChamberingThatStings + [9] = ChaosSpawnDrowningConstantly + [10] = ChaosSpawnIDontTrustEm + [11] = ChaosSpawnNotHereNotAgain + [12] = ChaosSpawnNotJustMe + [13] = ChaosSpawnThemAgain + [14] = ChaosSpawnThisIsntGonnaEndWell + [15] = DrawGoodToSeeYa + [16] = DrawHelloAgain + [17] = DrawHelloThere + [18] = DrawHello + [19] = DrawHeyHey + [20] = DrawHowsItGoin + [21] = DroppedDontForgetAboutMe + [22] = DroppedGuessIllWaitHere + [23] = DroppedIllCatchUp + [24] = DroppedSeeyaLaterBoss + [25] = DroppedTakeCareBoss + [26] = DroppedWatchThePaint + [27] = HolsterBeHereIfYouNeedMe + [28] = HolsterByeBye + [29] = HolsterFairEnough + [30] = HolsterGoodbye + [31] = HolsterIsTheSafetyOn2 + [32] = HolsterIsTheSafetyOn + [33] = HolsterOffIGo + [34] = HolsterYouKnowWhereToFindMeBoss + [35] = IdleChatterBarDownInQueens + [36] = IdleChatterDifferentFacility + [37] = IdleChatterGettingPaidForThisRight + [38] = IdleChatterSoundedLikeDeath + [39] = IdleChatterThoughtIHeardSomething + [40] = IdleChatterWhatWentWrong + [41] = IdleChatterYouGotAnyPlans + [42] = IdleChatterYouKnowWhereYoureGoin + [43] = MissedAlmostHitSomething + [44] = MissedILikeYourStyleBoss + [45] = MissedInvisHatFellaAround + [46] = MissedUhYouGoodBoss + [47] = MtfSpawnAhaReinforcements + [48] = MtfSpawnDontLetEmTakeMeAgain + [49] = MtfSpawnDontSayAnything + [50] = MtfSpawnFriendsOfYours + [51] = MtfSpawnGotAPlan + [52] = MtfSpawnThatsNotGood + [53] = MtfSpawnThingsAreGettingInterestingNow + [54] = MtfSpawnWereGettingBackup + [55] = OnKillBang + [56] = OnKillBeatItBopIt + [57] = OnKillGoodOneBoss + [58] = OnKillGoodStuff + [59] = OnKillGoodWorkBoss + [60] = OnKillNiceOneBoss + [61] = OnKillNiceShot + [62] = OnKillOooNice + [63] = OnKillRattleHim + [64] = OnKillScram + [65] = OnKillWhamo + [66] = PickupChamberNecksnapMcgee + [67] = PickupChamberPleasedToMeetYa + [68] = PickupChamberRoomForOneMore + [69] = PickupChamberSayWhatYearIsIt + [70] = PickupChamberSweetOxygen + [71] = PickupChamberWaterboarding + [72] = PickupChaosGasMask + [73] = PickupChaosSickGladRagsBoss + [74] = PickupChaosYouFellasFromOuttaTown + [75] = PickupDclassCouplaCorpses + [76] = PickupDclassNeedAHand + [77] = PickupDclassPartnersInCrime + [78] = PickupDclassTagEmBoss + [79] = PickupDclassUsedToOwnAJumpsuit + [80] = PickupDclassWooBreakout + [81] = PickupGenericEasyOnTheGrip + [82] = PickupGenericHeyBossHowsItGoin + [83] = PickupGenericSobbing + [84] = PickupGenericTakeMeWith + [85] = PickupMtfAnyChanceICanGo + [86] = PickupMtfAwJeezRick + [87] = PickupScientistDidntYouStickMeInThatTank + [88] = PickupScientistThoseGlassesBetterBe + [89] = PickupScientistYouDoKnowHowToShootRight + [90] = PickupTutorialHereToRescueMe + [91] = PickupTutorialOutOfPlaceHereBoss + [92] = RankupBetterThanExpected + [93] = RankupDoinPrettyGood + [94] = RankupFeelsGoodToBeBack + [95] = RankupIThinkWereWinning + [96] = RankupManicLaughter + [97] = RankupPartnersInCrimeOrJustice + [98] = ScpKilled049AppleADay + [99] = ScpKilled049BehindThatBeak + [100] = ScpKilled049IAmTheBlackDeath + [101] = ScpKilled049MsBirdface + [102] = ScpKilled049OverdosedOnShells + [103] = ScpKilled049ResurrectThis + [104] = ScpKilled049TakeGoodCareOfTheKids + [105] = ScpKilled0790101Moron + [106] = ScpKilled079AintDyinToAMinifridge + [107] = ScpKilled079KeepTheCamera + [108] = ScpKilled079NeverLikedThatToaster + [109] = ScpKilled079WarrantyRunOut + [110] = ScpKilled0967FeetTall + [111] = ScpKilled096AllTheRage + [112] = ScpKilled096IMayNotHaveEyes2 + [113] = ScpKilled096IMayNotHaveEyes + [114] = ScpKilled096LikedTheStatue + [115] = ScpKilled096ThoughtHedNeverShutUp + [116] = ScpKilled106HaveFunInTheCage + [117] = ScpKilled106OldDudesGone + [118] = ScpKilled106PocketThisDimension + [119] = ScpKilled106SinkingFeeling + [120] = ScpKilled106StayForABit + [121] = ScpKilled106SunkIntoTheFloor + [122] = ScpKilled173EyeOpener + [123] = ScpKilled173PeiceThatBackTogether + [124] = ScpKilled173ScrapingWasGettingOnMyNerves + [125] = ScpKilled173ShatteredLikeGlass + [126] = ScpKilled939NothingToSayNow + [127] = ScpKilled939StayDownFreakshow + [128] = ScpKilled939YouAintTheFirst2 + [129] = ScpKilled939YouAintTheFirst + [130] = ScpKilled3114NoMoreBones + [131] = ScpKilled3114OneLessSkeleton + [132] = ScpKilled3114WeGotTheSkinwalker + [133] = ScpKilledAllThoseFreakyPowers + [134] = ScpKilledCoupleOfRounds + [135] = ScpKilledGunBeatsWhateverYouAre2 + [136] = ScpKilledGunBeatsWhateverYouAre + [137] = ScpKilledNotSoTough + [138] = ScpKilledWhyBuildAContainer + [139] = ScpKilledZombieAintNoPill + [140] = ScpKilledZombieGetHisGoons + [141] = ScpKilledZombieMr12FeetUnder + [142] = ScpKilledZombieSayGoodbyeToUrGoons + [143] = ScpKilledZombieStayDown + [144] = ScpKilledZombieTwoLivesTooMany + [145] = ScpKilledZombieWhatGetsUp + [146] = UserKilledAttacthmentIssues + [147] = UserKilledBooooosss + [148] = UserKilledHeyHeyGetUp + [149] = UserKilledJustStartingToLikeYou + [150] = UserKilledNoNotNow2 + [151] = UserKilledNoNotNow + [152] = UserKilledNotAgain2 + [153] = UserKilledNotAgain +``` + +
+ ### Scp1344Status
InventorySystem.Items.Usables.Scp1344.Scp1344Status @@ -4436,6 +4780,16 @@ Last Update (14.0.0.2)
+### ScpSetting + +
UserSettings.OtherSettings.ScpSetting + +``` + [0] = ScpOptOut +``` + +
+ ### SecurityLevel
EncryptedChannelManager+SecurityLevel @@ -4459,6 +4813,19 @@ Last Update (14.0.0.2)
+### SequenceState + +
Interactables.Interobjects.CheckpointDoor+SequenceState + +``` + [0] = Idle + [1] = Granted + [2] = OpenLoop + [3] = ClosingWarning +``` + +
+ ### ServerLogType
ServerLogs+ServerLogType @@ -4636,6 +5003,17 @@ Last Update (14.0.0.2)
+### StatMessageType + +
PlayerStatsSystem.SyncedStatMessages+StatMessageType + +``` + [0] = CurrentValue + [1] = MaxValue +``` + +
+ ### StatusType
InventorySystem.Items.Usables.StatusMessage+StatusType @@ -4881,6 +5259,7 @@ Last Update (14.0.0.2) [4] = Trigger [8] = Tokens [11] = All + [16] = Spawn ```
@@ -5054,6 +5433,19 @@ Last Update (14.0.0.2) +### VoiceLinePriority + +
InventorySystem.Items.Firearms.Modules.Scp127.Scp127VoiceTriggerBase+VoiceLinePriority + +``` + [0] = Low + [1] = Normal + [2] = High + [3] = VeryHigh +``` + +
+ ### VoiceLinesName
PlayerRoles.PlayableScps.Scp3114.Scp3114VoiceLines+VoiceLinesName @@ -5080,6 +5472,8 @@ Last Update (14.0.0.2) [2] = SoundEffects [3] = MenuMusic [4] = MenuUI + [5] = Scp127Voice + [6] = Scp3114Voice ```
@@ -5111,12 +5505,13 @@ Last Update (14.0.0.2) ### WearableElements -
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.WearableElements +
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables.WearableElements ``` [0] = None [1] = Scp268Hat [2] = Scp1344Goggles + [4] = Armor ```
@@ -5200,7 +5595,7 @@ Last Update (14.0.0.2)
Damage Handlers -```md title="Latest Updated: 14.0.0.2" +```md title="Latest Updated: 14.1.0.0" All available DamageHandlers + Symbol ':' literally means "inherits from" @@ -5210,6 +5605,7 @@ All available DamageHandlers Scp956DamageHandler : StandardDamageHandler SnowballDamageHandler : AttackerDamageHandler PlayerStatsSystem.CustomReasonDamageHandler : StandardDamageHandler +PlayerStatsSystem.CustomReasonFirearmDamageHandler : FirearmDamageHandler PlayerStatsSystem.DisruptorDamageHandler : AttackerDamageHandler PlayerStatsSystem.ExplosionDamageHandler : AttackerDamageHandler PlayerStatsSystem.FirearmDamageHandler : AttackerDamageHandler diff --git a/EXILED/docs/articles/contributing/index.md b/EXILED/docs/articles/contributing/index.md index b721accdf3..fdcec6d5a0 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/ExMod-Team/EXILED). +First, create a fork of our [GitHub repository](https://github.com/ExSLMod-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://ExMod-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://ExSLMod-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 d2b4550e2e..d8268593d6 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/ExMod-Team/EXILED/releases). +Download `Exiled.Installer-Linux` from [here](https://github.com/ExSLMod-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 61bc27e7b7..c9e890ea74 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/ExMod-Team/EXILED/releases). +Download `Exiled.Installer-Win.exe` from [here](https://github.com/ExSLMod-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 4b38c89fcd..9b8cb7c3d9 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/ExMod-Team/EXILED/releases/). +You can select a release inside [our official GitHub repo](https://github.com/ExSLMod-Team/EXILED/releases/). ### Download the release diff --git a/EXILED/docs/docfx.json b/EXILED/docs/docfx.json index 4991f54874..8dc6b28e44 100644 --- a/EXILED/docs/docfx.json +++ b/EXILED/docs/docfx.json @@ -27,7 +27,7 @@ "_enableSearch": true, "_lang": "en", "_gitContribute": { - "repo": "https://github.com/ExMod-Team/EXILED", + "repo": "https://github.com/ExSLMod-Team/EXILED", "branch": "dev" } }, diff --git a/EXILED/docs/docs.csproj b/EXILED/docs/docs.csproj index d8e9026b98..f315b6977d 100644 --- a/EXILED/docs/docs.csproj +++ b/EXILED/docs/docs.csproj @@ -35,7 +35,7 @@ - + diff --git a/EXILED/docs/toc.yml b/EXILED/docs/toc.yml index 4e78e3efc1..f3e6ee456a 100644 --- a/EXILED/docs/toc.yml +++ b/EXILED/docs/toc.yml @@ -5,6 +5,4 @@ - name: NW Documentation href: articles/SCPSLRessources/NW_Documentation.html - name: Repository - href: https://github.com/ExMod-Team/EXILED -- name: Plugins - href: https://hub.exiled-team.net/ + href: https://github.com/ExSLMod-Team/EXILED From 26321c625b5ab1f46b81bb561519f7e980bd38f6 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:20:16 +0200 Subject: [PATCH 002/224] fix: Fix staff message wrong target (#542) Update Map.cs --- EXILED/Exiled.API/Features/Map.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index add2a5123b..f64bf32c95 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -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); } } @@ -420,4 +419,4 @@ internal static void ClearCache() #pragma warning restore CS0618 } } -} +} \ No newline at end of file From c5c886e264879d76a2548578f5f093134f09a5b4 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:20:32 +0200 Subject: [PATCH 003/224] fix: fix being observed calling in reverse (#541) Update BeingObserved.cs --- .../Patches/Events/Scp173/BeingObserved.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 From 6760ae1ff1b27008b7c3c368f3426d8c6cf53dc6 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:20:45 +0200 Subject: [PATCH 004/224] fix: CustomKeycard being blank and particle disruptor crashing (#540) customkeycard fix particle disruptor fix --- .../API/Features/CustomKeycard.cs | 3 +-- .../Patches/Events/Scp914/UpgradingPlayer.cs | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) 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.Events/Patches/Events/Scp914/UpgradingPlayer.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs index 394fd8815b..cffd499ea9 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 plugin_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, plugin_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 From 5f5600d203fc5add5653c3f1d8efa042e7b00303 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:20:57 +0200 Subject: [PATCH 005/224] fix: Fixes spawningItem transpiler (#539) * Update SpawningItem.cs * spacing --- .../Patches/Events/Map/SpawningItem.cs | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) 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]; From 8e10d6f8d6701a02eab581fb691d3898700c3f82 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 12 Jun 2025 19:47:35 +0200 Subject: [PATCH 006/224] 14.1.1 TT File Update --- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- .../SCPSLRessources/NW_Documentation.md | 88 +++++++++++-------- 2 files changed, 53 insertions(+), 37 deletions(-) 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/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 31ee11803d..bfc0f31671 100644 --- a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md +++ b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md @@ -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 From 4c53c0b3ed4d300cfb31fff9248d40b6276dba93 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Thu, 12 Jun 2025 20:02:53 +0200 Subject: [PATCH 007/224] Some fixes --- EXILED/EXILED.props | 2 +- .../Features/Core/UserSettings/KeybindSetting.cs | 14 ++++++++++++-- EXILED/Exiled.API/Features/Player.cs | 11 ++++++++--- .../Patches/Events/Player/Interacted.cs | 9 ++++----- .../Patches/Events/Warhead/ChangingLeverStatus.cs | 7 ++++--- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index eefc8a1140..6ff3aa50c2 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.0 + 9.6.1 false diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index cfb6b3bbca..a86f3269f2 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -25,11 +25,12 @@ 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) + 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 +62,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/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 4dfcf09409..6bb4124324 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1163,6 +1163,11 @@ public bool IsSpawnProtected ///
protected HealthStat CustomHealthStat { get; set; } + /// + /// Gets or sets a . + /// + protected FpcScaleController ScaleController { get; set; } + /// /// Converts LabApi player to EXILED player. /// @@ -2072,7 +2077,7 @@ public void SetScale(Vector3 scale, IEnumerable viewers) try { - ReferenceHub.transform.localScale = scale; + ScaleController.Scale = scale; foreach (Player target in viewers) Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); @@ -2094,12 +2099,12 @@ public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) try { - ReferenceHub.transform.localScale = fakeScale; + ScaleController.Scale = fakeScale; foreach (Player target in viewers) Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - ReferenceHub.transform.localScale = currentScale; + ScaleController.Scale = currentScale; } catch (Exception ex) { 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 event. ///
[EventPatch(typeof(Warhead), nameof(Warhead.ChangingLeverStatus))] - [HarmonyPatch(typeof(PlayerInteract), nameof(PlayerInteract.UserCode_CmdUsePanel__AlphaPanelOperations))] + + // [HarmonyPatch(typeof(PlayerInteract), nameof(PlayerInteract.UserCode_CmdUsePanel__AlphaPanelOperations))] internal static class ChangingLeverStatus { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + /*private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); @@ -72,6 +73,6 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); - } + }*/ } } \ No newline at end of file From 2e34c4b6f393dc047a42549eddd0e1be51ef634f Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Fri, 13 Jun 2025 03:04:04 +0900 Subject: [PATCH 008/224] fix: player.MessageTranslated kick players (#545) fix player.MessageTranslated kick players --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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); } } } From c86ff0448b1d15d8121e921b67f096562b592dd6 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Fri, 13 Jun 2025 00:13:13 +0200 Subject: [PATCH 009/224] Update workflows --- .github/workflows/dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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: From d44bedf7661800c18b027d6426f24dfeb35ae5fd Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:05:06 -0400 Subject: [PATCH 010/224] fix: KeybindSetting and RoundEnd transpiler (#546) * fix KeybindSetting and RoundEnd transpiler I think it all works, I ran server and no errors, I got on and ended round and no errors * Fix Scale (ScaleController is mid) Fix ChangingLeverStatus event I think these work --- .../Core/UserSettings/KeybindSetting.cs | 17 +++++++++++++++++ EXILED/Exiled.API/Features/Player.cs | 11 +++-------- .../Patches/Events/Server/RoundEnd.cs | 4 ++-- .../Events/Warhead/ChangingLeverStatus.cs | 15 +++++++-------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index a86f3269f2..9fc693b7ee 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -18,6 +18,23 @@ namespace Exiled.API.Features.Core.UserSettings ///
public class KeybindSetting : SettingBase, IWrapper { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + [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. /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 6bb4124324..4dfcf09409 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1163,11 +1163,6 @@ public bool IsSpawnProtected ///
protected HealthStat CustomHealthStat { get; set; } - /// - /// Gets or sets a . - /// - protected FpcScaleController ScaleController { get; set; } - /// /// Converts LabApi player to EXILED player. /// @@ -2077,7 +2072,7 @@ public void SetScale(Vector3 scale, IEnumerable viewers) try { - ScaleController.Scale = scale; + ReferenceHub.transform.localScale = scale; foreach (Player target in viewers) Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); @@ -2099,12 +2094,12 @@ public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) try { - ScaleController.Scale = fakeScale; + ReferenceHub.transform.localScale = fakeScale; foreach (Player target in viewers) Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - ScaleController.Scale = currentScale; + ReferenceHub.transform.localScale = currentScale; } catch (Exception ex) { 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 02596adcbc..bbcdd724e7 100644 --- a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs +++ b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs @@ -22,33 +22,32 @@ 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) + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); Label returnLabel = generator.DefineLabel(); - int offset = 2; - int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Brtrue_S) + offset; + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Call); 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 @@ -73,6 +72,6 @@ internal static class ChangingLeverStatus yield return newInstructions[z]; ListPool.Pool.Return(newInstructions); - }*/ + } } } \ No newline at end of file From d9a0896d126fad7ddd7e9831e1603c9c160e31b5 Mon Sep 17 00:00:00 2001 From: x3rt Date: Thu, 12 Jun 2025 21:43:42 -0600 Subject: [PATCH 011/224] Fix DroppedItem Event (#548) Fix dropping/dropped item event --- EXILED/Exiled.Events/Patches/Events/Player/DroppingItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 Date: Fri, 13 Jun 2025 02:40:52 -0600 Subject: [PATCH 012/224] Fix door interaction patch --- EXILED/Exiled.Events/Patches/Events/Player/InteractingDoor.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/InteractingDoor.cs b/EXILED/Exiled.Events/Patches/Events/Player/InteractingDoor.cs index dcdb2911f3..ddd406ac1f 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/InteractingDoor.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/InteractingDoor.cs @@ -55,10 +55,6 @@ private static IEnumerable Transpiler(IEnumerable Date: Fri, 13 Jun 2025 11:30:39 +0200 Subject: [PATCH 013/224] fix: Update repository URLs to reflect team name change (#552) --- .github/workflows/docs.yml | 2 +- .github/workflows/labapi.yml | 74 ------------------- .github/workflows/main.yml | 2 +- .github/workflows/push_nuget.yml | 2 +- .github/workflows/release.yml | 4 +- EXILED/EXILED.props | 4 +- .../Features/DatabaseHandler.cs | 2 +- EXILED/Exiled.Installer/README.md | 4 +- EXILED/Exiled.Loader/Updater.cs | 2 +- EXILED/Exiled/Exiled.nuspec | 6 +- EXILED/docs/articles/contributing/index.md | 4 +- .../articles/installation/automatic/linux.md | 2 +- .../installation/automatic/windows.md | 2 +- EXILED/docs/articles/installation/manual.md | 2 +- EXILED/docs/docfx.json | 2 +- EXILED/docs/toc.yml | 2 +- 16 files changed, 21 insertions(+), 95 deletions(-) delete mode 100644 .github/workflows/labapi.yml 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 29175ffee8..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/LabAPI.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 9b1b16ee05..b7c930b5e1 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/LabAPI.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/download/v1.8.1/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/Exmod-Team/EXILED-DLL-Archiver/releases/download/v1.8.1/EXILED-DLL-Archiver.exe jobs: build: diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 6ff3aa50c2..a4e5a63b69 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -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.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.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/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index a90f0589b0..0c70548bb4 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -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/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 From 06b97afe3c863471c9c62887522b0720ad1defa0 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:02:56 +0200 Subject: [PATCH 014/224] fix: Update RepoID to reflect new repository identifier --- EXILED/Exiled.Installer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From bcd6121ea872ca27cb6ce103b6c053fce600d80b Mon Sep 17 00:00:00 2001 From: Bolton <48883340+BoltonDev@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:32:49 +0200 Subject: [PATCH 015/224] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7c930b5e1..f62285d22a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ defaults: env: 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/Exmod-Team/EXILED-DLL-Archiver/releases/download/v1.8.1/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/Exmod-Team/EXILED-DLL-Archiver/releases/download/v1.9.0/EXILED-DLL-Archiver.exe jobs: build: From daaa1ebcd09b04c4bea7de5b0829b7b67170c795 Mon Sep 17 00:00:00 2001 From: Bolton <48883340+BoltonDev@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:40:31 +0200 Subject: [PATCH 016/224] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f62285d22a..c4edbf2994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ defaults: env: 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/Exmod-Team/EXILED-DLL-Archiver/releases/download/v1.9.0/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: From f9f3656365bb6a19567597272a77664389cf98a8 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:12:30 +0300 Subject: [PATCH 017/224] fix: Critical CustomRole api Error & Invalid ragdoll error (#560) * Added Safety Checks * Added SpawningRagdoll event for Safety Checks * Added Safety Checks * Simplfy * Simplfy --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- EXILED/Exiled.Events/Events.cs | 4 +++- EXILED/Exiled.Events/Handlers/Internal/Round.cs | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index ba6ecc95f8..ffb364760e 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -936,7 +936,7 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player)) + if (Check(ev.Player) && !Role.IsDead() && Role != RoleTypeId.Scp079) ev.Role = Role; } diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 103ba58829..80173fa27a 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -68,6 +68,7 @@ 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; @@ -105,6 +106,7 @@ 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; @@ -161,4 +163,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/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 99212af062..7df3f63007 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -82,6 +82,13 @@ public static void OnChangingRole(ChangingRoleEventArgs ev) ev.Player.Inventory.ServerDropEverything(); } + /// + public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) + { + if (ev.Role.IsDead() || ev.Role == RoleTypeId.Scp079) + ev.IsAllowed = false; + } + /// public static void OnActivatingSense(ActivatingSenseEventArgs ev) { From 5a55e62083024b04fd5f40f8a095d7a41fcd23b2 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Thu, 19 Jun 2025 09:59:11 +0200 Subject: [PATCH 018/224] fix: Update spawning logic to use IsFpcRole for role validation (#573) fix: Update spawning logic to use IsFpcRole for role vdation --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- EXILED/Exiled.Events/Handlers/Internal/Round.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index ffb364760e..97790a3e24 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -936,7 +936,7 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player) && !Role.IsDead() && Role != RoleTypeId.Scp079) + if (Check(ev.Player) && Role.IsFpcRole()) ev.Role = Role; } diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 7df3f63007..ae8dbb5b4e 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -85,7 +85,7 @@ public static void OnChangingRole(ChangingRoleEventArgs ev) /// public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (ev.Role.IsDead() || ev.Role == RoleTypeId.Scp079) + if (ev.Role.IsDead() || ev.Role.IsFpcRole()) ev.IsAllowed = false; } From 23ae2fdbd1d857fbc48519d27cfdddf3ee0d6b58 Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:01:51 +0900 Subject: [PATCH 019/224] fix: DoorType.Scp173NewGate is DoorType.Scp049Gate (#569) Update Door.cs --- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index c2cea9a761..22b0915132 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -600,7 +600,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 From e38d4b628a14e17acb197a0ab9c04ad8188fd6fd Mon Sep 17 00:00:00 2001 From: Naxefir <143349993+NaxefirYT@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:02:20 +0300 Subject: [PATCH 020/224] fix: RemainingDecontaminationTime is wrong (#572) Update Map.cs --- EXILED/Exiled.API/Features/Map.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index f64bf32c95..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. @@ -419,4 +419,4 @@ internal static void ClearCache() #pragma warning restore CS0618 } } -} \ No newline at end of file +} From 4ee1686d9ddd0448d49ac821929117e151a3e5e0 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:13:12 +0200 Subject: [PATCH 021/224] fix: Correct role validation logic in spawning events --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- EXILED/Exiled.Events/Handlers/Internal/Round.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 97790a3e24..8e1620730f 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -936,7 +936,7 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player) && Role.IsFpcRole()) + if (Check(ev.Player) && !Role.IsFpcRole()) ev.Role = Role; } diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index ae8dbb5b4e..f81b0886b9 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -85,7 +85,7 @@ public static void OnChangingRole(ChangingRoleEventArgs ev) /// public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (ev.Role.IsDead() || ev.Role.IsFpcRole()) + if (ev.Role.IsDead() || !ev.Role.IsFpcRole()) ev.IsAllowed = false; } From 6650b959fb1c230da3bf9b6e99292261464be3ab Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:17:43 +0900 Subject: [PATCH 022/224] feat: Player hint update 14.1.1 (#549) * for 14.1.1 hint update * Resolve recursive call (sry my mistake) --- EXILED/Exiled.API/Features/Player.cs | 29 +++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 4dfcf09409..67353fb2ba 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3020,9 +3020,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)); } /// From da8b88453281a7edf635b41b07d8ed876c1a47d0 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Fri, 20 Jun 2025 01:19:14 +0300 Subject: [PATCH 023/224] Fix custom role ragdoll chechk --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 8e1620730f..97790a3e24 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -936,7 +936,7 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player) && !Role.IsFpcRole()) + if (Check(ev.Player) && Role.IsFpcRole()) ev.Role = Role; } From 463fd1fcf2ae5fece28d0e74b2c992419413e02c Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 5 Jul 2025 16:03:47 +0200 Subject: [PATCH 024/224] Missing ServerRoom Elevator recognised as Unknown --- EXILED/Exiled.API/Features/Lift.cs | 1 + 1 file changed, 1 insertion(+) 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, From 79d5f41da2ebb706e246016fddaacd3815e26d76 Mon Sep 17 00:00:00 2001 From: officialvirtualrain <112497305+officialvirtualrain@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:07:20 +0400 Subject: [PATCH 025/224] fix: CustomRoles GotRoleHint.Show config (#583) added GotRoleHint.Show config check --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 97790a3e24..2a4796bd10 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -578,7 +578,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); From a3a650df2269bafd2c55ec702e26bc7abb8d56d3 Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Sat, 5 Jul 2025 23:09:21 +0900 Subject: [PATCH 026/224] feat: Add UpgradedInventoryItem and UpgradedPickup on Scp914 (#579) * Add UpgradedInventoryItem and UpgradedPickup * please * Improved code (thanks to @MS-crew) --- .../Scp914/UpgradedInventoryItemEventArgs.cs | 65 ++++++++++++++ .../Scp914/UpgradedPickupEventArgs.cs | 64 ++++++++++++++ EXILED/Exiled.Events/Handlers/Scp914.cs | 22 +++++ .../Patches/Events/Scp914/UpgradedPickup.cs | 73 ++++++++++++++++ .../Patches/Events/Scp914/UpgradedPlayer.cs | 84 +++++++++++++++++++ .../{UpgradingItem.cs => UpgradingPickup.cs} | 4 +- .../Patches/Events/Scp914/UpgradingPlayer.cs | 4 +- 7 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPlayer.cs rename EXILED/Exiled.Events/Patches/Events/Scp914/{UpgradingItem.cs => UpgradingPickup.cs} (97%) 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/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/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 cffd499ea9..3927ed9a55 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs @@ -116,14 +116,14 @@ private static IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Stloc_S && x.operand is LocalBuilder { LocalIndex: 10 }) + offset; - ConstructorInfo plugin_api_constructor = typeof(LabApi.Events.Arguments.Scp914Events.Scp914ProcessingInventoryItemEventArgs) + 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, plugin_api_constructor)) + offset; + index = newInstructions.FindIndex(x => x.Is(OpCodes.Newobj, lab_api_constructor)) + offset; // ridtp lcz914 // noclip From 24795f4a2ced66ebfdda3de4f577bd91576ab0c6 Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Sun, 6 Jul 2025 00:43:22 +0900 Subject: [PATCH 027/224] fix: ChangingLeverStatusEventArgs (#582) * Resolve #581 * Update ChangingLeverStatus.cs * better implementation of the FindIndex --------- Co-authored-by: Yamato --- .../Patches/Events/Warhead/ChangingLeverStatus.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs index bbcdd724e7..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; @@ -36,7 +37,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Call); + int index = newInstructions.FindIndex(x => x.operand == (object)PropertySetter(typeof(AlphaWarheadNukesitePanel), nameof(AlphaWarheadNukesitePanel.Networkenabled))) - 5; newInstructions.InsertRange( index, From d80e4de7b45c83ee0cf9a225069418616c4078a9 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:28:18 +0300 Subject: [PATCH 028/224] feat: New Changed Aspect Ratio Event & Player::AspectRatio Property (#556) * Create AspectRatioType.cs * Create FloatExtensions.cs * Add Player Aspect Ratio Property * Create ChangedRatioEventArgs.cs * Added ChangedRatio handlers * Create ChangedAspectRatio.cs * Added 1_1 Square type * Added Unknow and 1_1 ratio --- EXILED/Exiled.API/Enums/AspectRatioType.cs | 60 +++++++++++++ .../Exiled.API/Extensions/FloatExtensions.cs | 56 ++++++++++++ EXILED/Exiled.API/Features/Player.cs | 5 ++ .../EventArgs/Player/ChangedRatioEventArgs.cs | 54 ++++++++++++ EXILED/Exiled.Events/Handlers/Player.cs | 11 +++ .../Events/Player/ChangedAspectRatio.cs | 85 +++++++++++++++++++ 6 files changed, 271 insertions(+) create mode 100644 EXILED/Exiled.API/Enums/AspectRatioType.cs create mode 100644 EXILED/Exiled.API/Extensions/FloatExtensions.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs 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/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/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 67353fb2ba..323b4ec463 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 . /// 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/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e35df7b044..b444aa4b4f 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. /// @@ -684,6 +689,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. /// 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); + } + } +} From b7df2924210150c7234ef3f2da195a536d88dbbe Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:52:22 +0900 Subject: [PATCH 029/224] feat: Server with more interaction (#535) --- EXILED/Exiled.API/Features/Server.cs | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) 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 From 573cc6a3c98ad53aeafc917691cbf40fa6d700ff Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:54:31 +0900 Subject: [PATCH 030/224] fix: Fix IsWeapon() did not check for jailbird (#584) * Jailbird * Update CustomWeapon.cs * Revert "Update CustomWeapon.cs" This reverts commit 4c5e275a8dd05a3e55ce800f68de27c7fcc54004. * Revert "Jailbird" This reverts commit 56e3d62afbd1b87504c1f47d5058138be9b09c29. * Approve Change Request * Better way --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs | 6 +++--- EXILED/Exiled.API/Extensions/ItemExtensions.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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/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. From 254c27a69f2cdf20cf759c7067edc8fe53cc8594 Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Sun, 6 Jul 2025 02:03:02 +0900 Subject: [PATCH 031/224] feat: Add Exiled.API.Features.Workstation (#568) * Add Exiled.API.Features.Workstation * fix * Approve the feedback * omg * fix: Workstation ctor could be called twice * use Transform --------- Co-authored-by: Yamato Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Workstation.cs | 167 ++++++++++++++++++ .../Patches/Generic/WorkstationListAdd.cs | 39 ++++ 2 files changed, 206 insertions(+) create mode 100644 EXILED/Exiled.API/Features/Workstation.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs 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.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 From f1d20072b1a1a02bcf3d2c5198e5f39cdf63b5a4 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 5 Jul 2025 20:03:32 +0300 Subject: [PATCH 032/224] fix: OnEscaping Exiled and LabAPI bug (#580) Fix --- EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 4a4fa0dcba..532c40f2f0 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 = true; } /// From 1aeeb082008566d9d82ea2c375d09b12fee13e1e Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 5 Jul 2025 20:06:01 +0300 Subject: [PATCH 033/224] feat: Rework and Fix Custom Role Spawn Logic & Limits (#578) * Update CustomRole.cs * Update CustomRoles.cs * Update PlayerHandlers.cs * Delete white space * Update PlayerHandlers.cs * Update CustomRole.cs * F' * Fix * Update PlayerHandlers.cs * Add Spawn reason Spawn reasons have been set to prevent custom role assignment in incorrect situation. * Update PlayerHandlers.cs * Update PlayerHandlers.cs * Update Extensions.cs * Update PlayerHandlers.cs * Update PlayerHandlers.cs * Update PlayerHandlers.cs * Update CustomRole.cs * Update PlayerHandlers.cs * Update CustomRole.cs * white space * Changed to thread safe * non blocking, thread safe implementation Refactored the player spawning logic to be lock free by using Interlocked for atomic counters and a ConcurrentDictionary for safe concurrent processing. * Update PlayerHandlers.cs * Update CustomRole.cs * Update CustomRole.cs * Delete log info * Update PlayerHandlers.cs * Update CustomRole.cs * rework real random and fair spawn position system * Update CustomRole.cs * Update CustomRole.cs * Update CustomRole.cs --- EXILED/Exiled.CustomRoles/API/Extensions.cs | 21 ++++- .../API/Features/CustomRole.cs | 78 ++++++++++------ EXILED/Exiled.CustomRoles/CustomRoles.cs | 10 ++- .../Events/PlayerHandlers.cs | 89 ++++++++++++++++++- 4 files changed, 167 insertions(+), 31 deletions(-) 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 2a4796bd10..5329d0f954 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -37,6 +37,14 @@ namespace Exiled.CustomRoles.API.Features /// public abstract class CustomRole { + /// + /// Gets or sets the number of players that naturally spawned with this custom role. + /// + [YamlIgnore] + #pragma warning disable SA1401 // Fields should be private + public int SpawnedPlayers = 0; + #pragma warning restore SA1401 // Fields should be private + private const float AddRoleDelay = 0.25f; private static Dictionary typeLookupTable = new(); @@ -816,46 +824,68 @@ 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.IsEmpty()) + { + foreach (StaticSpawnPoint sp in SpawnProperties.StaticSpawnPoints) + { + Add(sp.Position, sp.Chance); + } + } - if (SpawnProperties.StaticSpawnPoints.Count > 0) + if (!SpawnProperties.DynamicSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.StaticSpawnPoints) + foreach (DynamicSpawnPoint sp in SpawnProperties.DynamicSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.DynamicSpawnPoints.Count > 0) + if (!SpawnProperties.RoleSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.DynamicSpawnPoints) + foreach (RoleSpawnPoint sp in SpawnProperties.RoleSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoleSpawnPoints.Count > 0) + if (!SpawnProperties.RoomSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoleSpawnPoints) + foreach (RoomSpawnPoint sp in SpawnProperties.RoomSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoomSpawnPoints.Count > 0) + if (spawnPointPool.Count == 0 || totalchance <= 0f) + { + return Vector3.zero; + } + + float randomRoll = (float)(Loader.Random.NextDouble() * totalchance); + foreach ((float chance, Vector3 pos) in spawnPointPool) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoomSpawnPoints) + if (randomRoll < chance) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + return pos; } + + randomRoll -= chance; } return Vector3.zero; @@ -869,7 +899,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; } @@ -885,7 +914,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; } @@ -924,12 +952,6 @@ 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))) 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..5c0aa7356e 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; /// @@ -15,6 +23,15 @@ namespace Exiled.CustomRoles.Events public class PlayerHandlers { private readonly CustomRoles plugin; + private readonly HashSet validSpawnReasons = new() + { + SpawnReason.RoundStart, + SpawnReason.Respawn, + SpawnReason.LateJoin, + SpawnReason.Revived, + SpawnReason.Escaped, + SpawnReason.ItemUsage, + }; /// /// Initializes a new instance of the class. @@ -25,6 +42,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 +60,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 = Interlocked.Increment(ref candidateRole.SpawnedPlayers); + if (newSpawnCount <= candidateRole.SpawnProperties.Limit) + { + candidateRole.AddRole(ev.Player); + break; + } + else + { + Interlocked.Decrement(ref candidateRole.SpawnedPlayers); + randomRoll -= candidateRole.SpawnChance; + } + } + } } -} \ No newline at end of file +} From 44afe852f85b85d2853ffc49a0157f8003155c9b Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 5 Jul 2025 20:00:29 +0200 Subject: [PATCH 034/224] Bump Version --- EXILED/EXILED.props | 2 +- EXILED/docs/articles/SCPSLRessources/NW_Documentation.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index a4e5a63b69..9dfffe99fd 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.1 + 9.6.2 false diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index bfc0f31671..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 @@ -5611,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" From 28597de03674add9cb84b4d29d5f4d77ba9f043e Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 5 Jul 2025 23:33:00 +0200 Subject: [PATCH 035/224] Fix CustomRole Issue not working when using OnSpawned --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 5329d0f954..fc4e897edb 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -514,6 +514,7 @@ public virtual void AddRole(Player player) { Log.Debug($"{Name}: Adding role to {player.Nickname}."); player.UniqueRole = Name; + TrackedPlayers.Add(player); if (Role != RoleTypeId.None) { @@ -534,7 +535,6 @@ public virtual void AddRole(Player player) } player.UniqueRole = Name; - TrackedPlayers.Add(player); Timing.CallDelayed( AddRoleDelay, From 3c7ce4d9405307fc79a6d4d8363b33b6f38442f9 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sun, 6 Jul 2025 00:20:04 +0200 Subject: [PATCH 036/224] SpawnReason.CustomRole --- EXILED/Exiled.API/Enums/SpawnReason.cs | 5 +++++ EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Enums/SpawnReason.cs b/EXILED/Exiled.API/Enums/SpawnReason.cs index 8274b99336..4ef67f1742 100644 --- a/EXILED/Exiled.API/Enums/SpawnReason.cs +++ b/EXILED/Exiled.API/Enums/SpawnReason.cs @@ -61,5 +61,10 @@ public enum SpawnReason : byte // TOTO: Remove this file and use Basegame /// 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.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index fc4e897edb..0e0d8042b0 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -521,16 +521,16 @@ public virtual void AddRole(Player player) 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); } } @@ -954,7 +954,7 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) 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); } From eeca9e9fa5a7157dceec26361ba83bb07f80e934 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sun, 6 Jul 2025 18:30:38 +0200 Subject: [PATCH 037/224] fix up link --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7c930b5e1..e0c018e25a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ defaults: env: 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/Exmod-Team/EXILED-DLL-Archiver/releases/download/v1.8.1/EXILED-DLL-Archiver.exe + EXILED_DLL_ARCHIVER_URL: https://github.com/ExMod-Team/EXILED-DLL-Archiver/releases/latest/EXILED-DLL-Archiver.exe jobs: build: From e6f1edf2c4e3ed0637f97aad074029652babebfd Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sun, 6 Jul 2025 12:33:28 -0400 Subject: [PATCH 038/224] fix: fix bad implementation of a CustomRole Field (#586) * fix KeybindSetting and RoundEnd transpiler I think it all works, I ran server and no errors, I got on and ended round and no errors * Fix Scale (ScaleController is mid) Fix ChangingLeverStatus event I think these work * Fix goofy goober stuff --- .../Exiled.CustomRoles/API/Features/CustomRole.cs | 14 ++++++-------- EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs | 11 ++++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 0e0d8042b0..db5e599cd3 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -37,14 +37,6 @@ namespace Exiled.CustomRoles.API.Features ///
public abstract class CustomRole { - /// - /// Gets or sets the number of players that naturally spawned with this custom role. - /// - [YamlIgnore] - #pragma warning disable SA1401 // Fields should be private - public int SpawnedPlayers = 0; - #pragma warning restore SA1401 // Fields should be private - private const float AddRoleDelay = 0.25f; private static Dictionary typeLookupTable = new(); @@ -184,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. /// diff --git a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs index 5c0aa7356e..1a10e38dcf 100644 --- a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs +++ b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs @@ -22,8 +22,7 @@ namespace Exiled.CustomRoles.Events ///
public class PlayerHandlers { - private readonly CustomRoles plugin; - private readonly HashSet validSpawnReasons = new() + private static readonly HashSet ValidSpawnReasons = new() { SpawnReason.RoundStart, SpawnReason.Respawn, @@ -33,6 +32,8 @@ public class PlayerHandlers SpawnReason.ItemUsage, }; + private readonly CustomRoles plugin; + /// /// Initializes a new instance of the class. /// @@ -64,7 +65,7 @@ internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) /// internal void OnSpawned(SpawnedEventArgs ev) { - if (!validSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) + if (!ValidSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) { return; } @@ -108,7 +109,7 @@ internal void OnSpawned(SpawnedEventArgs ev) break; } - int newSpawnCount = Interlocked.Increment(ref candidateRole.SpawnedPlayers); + int newSpawnCount = candidateRole.SpawnedPlayers++; if (newSpawnCount <= candidateRole.SpawnProperties.Limit) { candidateRole.AddRole(ev.Player); @@ -116,7 +117,7 @@ internal void OnSpawned(SpawnedEventArgs ev) } else { - Interlocked.Decrement(ref candidateRole.SpawnedPlayers); + candidateRole.SpawnedPlayers--; randomRoll -= candidateRole.SpawnChance; } } From b896a73a32decb117268090f23ef29bebcf0322c Mon Sep 17 00:00:00 2001 From: Bonjemus <59416055+Bonjemus@users.noreply.github.com> Date: Mon, 7 Jul 2025 22:54:04 +0200 Subject: [PATCH 039/224] fix: Scale fix (#571) * scale fix first attempt * 2 attempt * 3 attempt * 4 attempt * Add fix for when player reconnect Scale not working * Remove TryCatch & error --------- Co-authored-by: Yamato --- EXILED/Exiled.API/Features/Player.cs | 46 +++++++------------ EXILED/Exiled.API/Features/Roles/FpcRole.cs | 9 ++++ .../API/Features/CustomRole.cs | 3 +- .../Exiled.Events/Handlers/Internal/Round.cs | 9 ++-- 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 323b4ec463..779982d2fb 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -699,7 +699,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public Vector3 Scale { get => ReferenceHub.transform.localScale; - set => SetScale(value, List); + set => SetScale(value); } /// @@ -2065,6 +2065,16 @@ public void Disconnect(string reason = null) => /// public void ResetStamina() => Stamina = StaminaStat.MaxValue; + /// + /// Sets the scale of a player on the server side. + /// + /// The scale to set. + public void SetScale(Vector3 scale) + { + ReferenceHub.transform.localScale = scale; + new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToAuthenticated(); + } + /// /// Sets the scale of a player on the server side. /// @@ -2072,20 +2082,8 @@ public void Disconnect(string reason = null) => /// Who should see the updated scale. public void SetScale(Vector3 scale, IEnumerable viewers) { - if (scale == Scale) - return; - - try - { - ReferenceHub.transform.localScale = scale; - - foreach (Player target in viewers) - Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - } - catch (Exception exception) - { - Log.Error($"{nameof(SetScale)} error: {exception}"); - } + ReferenceHub.transform.localScale = scale; + new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToHubsConditionally(x => x != null && viewers.Contains(Get(x))); } /// @@ -2095,21 +2093,9 @@ public void SetScale(Vector3 scale, IEnumerable viewers) /// Who should see the fake scale. public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) { - Vector3 currentScale = Scale; - - try - { - ReferenceHub.transform.localScale = fakeScale; - - foreach (Player target in viewers) - Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - - ReferenceHub.transform.localScale = currentScale; - } - catch (Exception ex) - { - Log.Error($"{nameof(SetFakeScale)}: {ex}"); - } + SyncedScaleMessages.ScaleMessage scaleMessage = new(fakeScale, ReferenceHub); + foreach (Player player in viewers) + player.Connection.Send(scaleMessage, 0); } /// diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index d1c0a12591..d54201ff0e 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -76,6 +76,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. /// diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index db5e599cd3..895b483b2e 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -564,7 +564,7 @@ public virtual void AddRole(Player player) Log.Debug($"{Name}: Setting health values."); player.Health = MaxHealth; player.MaxHealth = MaxHealth; - player.Scale = Scale; + Timing.CallDelayed(0.1f, () => player.Scale = Scale); // To fix : remove the delay in 14.1.2 once the crash issue is resolved if (Gravity.HasValue && player.Role is FpcRole fpcRole) fpcRole.Gravity = Gravity.Value; Vector3 position = GetSpawnPosition(); @@ -627,7 +627,6 @@ public virtual void RemoveRole(Player player) TrackedPlayers.Remove(player); player.CustomInfo = string.Empty; player.InfoArea |= PlayerInfoArea.Role | PlayerInfoArea.Nickname; - player.Scale = Vector3.one; if (CustomAbilities is not null) { foreach (CustomAbility ability in CustomAbilities) diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index f81b0886b9..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; /// @@ -114,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 }); } } From 6085b42cc10f2ceef477b9896399ea949b69e49b Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:47:44 +0900 Subject: [PATCH 040/224] feat: Add Ragdoll features & readability (#567) * Update Ragdoll.cs * fix 2 * Remove Check in case of people using Mirror --------- Co-authored-by: Yamato --- EXILED/Exiled.API/Features/Ragdoll.cs | 64 +++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) 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. /// From ba204e5967f8dc32764420b5cd603eb27fb2cec3 Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 8 Jul 2025 06:57:54 +0200 Subject: [PATCH 041/224] MissChanged from ExSLMod --- EXILED/Exiled.API/Features/Items/Throwable.cs | 5 ++ EXILED/Exiled.API/Features/Respawn.cs | 73 +++++++++++++++++++ EXILED/Exiled.API/Features/Toys/Capybara.cs | 6 +- EXILED/Exiled.API/Features/Warhead.cs | 6 +- .../Item/ChargingJailbirdEventArgs.cs | 14 ++-- .../Item/JailbirdChargeCompleteEventArgs.cs | 52 +++++++++++++ EXILED/Exiled.Events/Handlers/Item.cs | 15 +++- .../Patches/Events/Item/JailbirdPatch.cs | 16 +++- 8 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 EXILED/Exiled.Events/EventArgs/Item/JailbirdChargeCompleteEventArgs.cs 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/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/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.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/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/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index d8153a586e..af9dea76b5 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. /// @@ -102,11 +107,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. /// 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; From ac64e9220df9dc314c630ef3e23c0026c6c2b517 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Tue, 8 Jul 2025 08:05:58 +0300 Subject: [PATCH 042/224] feat: Adding base game All players spawned event for exiled (#558) * Add RoleAssigner.OnPlayersSpawned * Add Handler --- EXILED/Exiled.Events/Events.cs | 4 +++- EXILED/Exiled.Events/Handlers/Server.cs | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 103ba58829..79ee8b0c4d 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -73,6 +73,7 @@ public override void OnEnabled() Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; CharacterClassManager.OnRoundStarted += Handlers.Server.OnRoundStarted; + RoleAssigner.OnPlayersSpawned += Handlers.Server.OnAllPlayersSpawned; WaveManager.OnWaveSpawned += Handlers.Server.OnRespawnedTeam; InventorySystem.InventoryExtensions.OnItemAdded += Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved += Handlers.Player.OnItemRemoved; @@ -113,6 +114,7 @@ public override void OnDisabled() InventorySystem.InventoryExtensions.OnItemAdded -= Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved -= Handlers.Player.OnItemRemoved; + RoleAssigner.OnPlayersSpawned -= Handlers.Server.OnAllPlayersSpawned; WaveManager.OnWaveSpawned -= Handlers.Server.OnRespawnedTeam; RagdollManager.OnRagdollSpawned -= Handlers.Internal.RagdollList.OnSpawnedRagdoll; RagdollManager.OnRagdollRemoved -= Handlers.Internal.RagdollList.OnRemovedRagdoll; @@ -161,4 +163,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/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 +} From dfd76ea29f6080e556440e9cf1bf92980562ae6c Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 8 Jul 2025 07:07:07 +0200 Subject: [PATCH 043/224] 9.6.3 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 9dfffe99fd..02d86cf5cc 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.2 + 9.6.3 false From 7ea3cccc1ae54ae3277b6fa79c3777c49a7b4392 Mon Sep 17 00:00:00 2001 From: TiBarification Date: Tue, 8 Jul 2025 08:11:01 +0300 Subject: [PATCH 044/224] fix: bug with not tracking limit of spawned players in custom roles (#585) fixes bug with not tracking limit of spawned players Co-authored-by: TiBarification <6222472+TiBarification@users.noreply.github.com> --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index ba6ecc95f8..67c3826408 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -924,7 +924,7 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) private void OnInternalSpawned(SpawnedEventArgs ev) { - if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance) + if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance && (SpawnProperties.Limit == 0 || TrackedPlayers.Count < SpawnProperties.Limit)) AddRole(ev.Player); } From 154ba8ded2e132483034baa58a0c9f818d3ed38b Mon Sep 17 00:00:00 2001 From: Bolton <48883340+BoltonDev@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:01:32 +0200 Subject: [PATCH 045/224] fix: release workflow --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0c018e25a..c4edbf2994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ defaults: env: 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/ExMod-Team/EXILED-DLL-Archiver/releases/latest/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: From cbc5a25abeb04b1bfdb5bd630587ecf32eedcffc Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 17 Jul 2025 20:59:54 +0200 Subject: [PATCH 046/224] why bruh nw add enum value in the middle --- EXILED/Exiled.API/Enums/SpawnReason.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/EXILED/Exiled.API/Enums/SpawnReason.cs b/EXILED/Exiled.API/Enums/SpawnReason.cs index 4ef67f1742..3bde3457df 100644 --- a/EXILED/Exiled.API/Enums/SpawnReason.cs +++ b/EXILED/Exiled.API/Enums/SpawnReason.cs @@ -57,6 +57,11 @@ 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. /// From 3faecbbe9ffdd3b717400894042001e44c09fdb9 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 18:13:21 +0200 Subject: [PATCH 047/224] Return LateJoin --- EXILED/Exiled.CustomItems/API/Features/CustomItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()) From 151bed1ce903a66454a063875437c2e0b81eec9f Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 18:15:32 +0200 Subject: [PATCH 048/224] Partially Revert "fix: Scale fix (#571)" This reverts commit b896a73a32decb117268090f23ef29bebcf0322c. Because of NW making crash on Player --- EXILED/Exiled.API/Features/Player.cs | 46 ++++++++++++------- .../API/Features/CustomRole.cs | 3 +- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 779982d2fb..323b4ec463 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -699,7 +699,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public Vector3 Scale { get => ReferenceHub.transform.localScale; - set => SetScale(value); + set => SetScale(value, List); } /// @@ -2065,16 +2065,6 @@ public void Disconnect(string reason = null) => /// public void ResetStamina() => Stamina = StaminaStat.MaxValue; - /// - /// Sets the scale of a player on the server side. - /// - /// The scale to set. - public void SetScale(Vector3 scale) - { - ReferenceHub.transform.localScale = scale; - new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToAuthenticated(); - } - /// /// Sets the scale of a player on the server side. /// @@ -2082,8 +2072,20 @@ public void SetScale(Vector3 scale) /// Who should see the updated scale. public void SetScale(Vector3 scale, IEnumerable viewers) { - ReferenceHub.transform.localScale = scale; - new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToHubsConditionally(x => x != null && viewers.Contains(Get(x))); + if (scale == Scale) + return; + + try + { + ReferenceHub.transform.localScale = scale; + + foreach (Player target in viewers) + Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); + } + catch (Exception exception) + { + Log.Error($"{nameof(SetScale)} error: {exception}"); + } } /// @@ -2093,9 +2095,21 @@ public void SetScale(Vector3 scale, IEnumerable viewers) /// Who should see the fake scale. public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) { - SyncedScaleMessages.ScaleMessage scaleMessage = new(fakeScale, ReferenceHub); - foreach (Player player in viewers) - player.Connection.Send(scaleMessage, 0); + Vector3 currentScale = Scale; + + try + { + ReferenceHub.transform.localScale = fakeScale; + + foreach (Player target in viewers) + Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); + + ReferenceHub.transform.localScale = currentScale; + } + catch (Exception ex) + { + Log.Error($"{nameof(SetFakeScale)}: {ex}"); + } } /// diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 895b483b2e..db5e599cd3 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -564,7 +564,7 @@ public virtual void AddRole(Player player) Log.Debug($"{Name}: Setting health values."); player.Health = MaxHealth; player.MaxHealth = MaxHealth; - Timing.CallDelayed(0.1f, () => player.Scale = Scale); // To fix : remove the delay in 14.1.2 once the crash issue is resolved + player.Scale = Scale; if (Gravity.HasValue && player.Role is FpcRole fpcRole) fpcRole.Gravity = Gravity.Value; Vector3 position = GetSpawnPosition(); @@ -627,6 +627,7 @@ public virtual void RemoveRole(Player player) TrackedPlayers.Remove(player); player.CustomInfo = string.Empty; player.InfoArea |= PlayerInfoArea.Role | PlayerInfoArea.Nickname; + player.Scale = Vector3.one; if (CustomAbilities is not null) { foreach (CustomAbility ability in CustomAbilities) From 5a74a1bb4dc09fc7ef00a643cf8d417fc80c96fc Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:16:49 +0300 Subject: [PATCH 049/224] feat: Add Model (#595) Update FpcRole.cs --- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index d54201ff0e..2abfc50377 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -67,6 +67,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. /// @@ -305,4 +314,4 @@ public void ResetStamina(bool multipliers = false) StaminaRegenMultiplier = 1f; } } -} \ No newline at end of file +} From 4296fa96dbee7ef55deb738b015ed5f4b9c9931c Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:17:58 +0300 Subject: [PATCH 050/224] feat: New Scp049 Finishing Sense Event (#559) * Create FinishingSenseEventArgs.cs * Add Finishing Sense Handler * Create FinishingSense.cs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Scp049/FinishingSenseEventArgs.cs | 66 +++++ EXILED/Exiled.Events/Handlers/Scp049.cs | 13 +- .../Patches/Events/Scp049/FinishingSense.cs | 258 ++++++++++++++++++ 3 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp049/FinishingSenseEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp049/FinishingSense.cs 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/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/Patches/Events/Scp049/FinishingSense.cs b/EXILED/Exiled.Events/Patches/Events/Scp049/FinishingSense.cs new file mode 100644 index 0000000000..9a63f16e7e --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp049/FinishingSense.cs @@ -0,0 +1,258 @@ +// ----------------------------------------------------------------------- +// +// 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); + } + } +} From c782ee288d25bbc0c4db42aa433ba83d0b4215a4 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:18:09 -0400 Subject: [PATCH 051/224] feat: RemovingTarget event for 096 (#576) * fix KeybindSetting and RoundEnd transpiler I think it all works, I ran server and no errors, I got on and ended round and no errors * Fix Scale (ScaleController is mid) Fix ChangingLeverStatus event I think these work * Add event (was painful cuz of previous shenanigans) --- .../Scp096/RemovingTargetEventArgs.cs | 59 +++++++ EXILED/Exiled.Events/Handlers/Scp096.cs | 11 ++ .../Patches/Events/Scp096/RemovingTarget.cs | 149 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs 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/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/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 From b3564e126a15fa6c3d90e2ac0f47275a471751af Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:18:26 +0300 Subject: [PATCH 052/224] fix: Escaping Exiled and LabAPI bug different way (#589) * . * . --- EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs | 2 +- .../Patches/Events/Player/EscapingAndEscaped.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 532c40f2f0..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 = true; + IsAllowed = escapeScenario is not EscapeScenario.None; } /// 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, From 1c2792a7badc4e54730b644ebf74b4a75e31238a Mon Sep 17 00:00:00 2001 From: R2kip <56793105+R2kip@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:18:57 +0300 Subject: [PATCH 053/224] feat: Add ragdoll to died event (#507) * Add ragdoll to died event * oops * oops 2x :) --- .../EventArgs/Player/DiedEventArgs.cs | 14 ++++++++++++-- .../Patches/Events/Player/DyingAndDied.cs | 11 ++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) 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/Patches/Events/Player/DyingAndDied.cs b/EXILED/Exiled.Events/Patches/Events/Player/DyingAndDied.cs index 9b4c29b3b1..489732cf08 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/DyingAndDied.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/DyingAndDied.cs @@ -19,6 +19,7 @@ namespace Exiled.Events.Patches.Events.Player using HarmonyLib; using PlayerRoles; + using PlayerRoles.Ragdolls; using PlayerStatsSystem; @@ -41,6 +42,7 @@ private static 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 +} From 0778174e0d54c31eb7f97e957245d4d2a95306c4 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:20:41 +0300 Subject: [PATCH 054/224] feat: Generic damage handler add new value (#561) * Clean * Update GenericDamageHandler.cs * Cleanup * Remove BreakingChange * Fix Ambiguous when compiling --------- Co-authored-by: Yamato Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Enums/DamageType.cs | 5 ++--- .../DamageHandlers/GenericDamageHandler.cs | 8 ++++++-- EXILED/Exiled.API/Features/Player.cs | 14 +++++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) 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/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/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 323b4ec463..1689744773 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2149,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. /// From 5e50a6e6f047dfd714a6bc60d86cf722b8c84e78 Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Fri, 1 Aug 2025 01:21:37 +0900 Subject: [PATCH 055/224] feat: Add Wave related functions to Respawn (#536) Update Respawn.cs From 98e025c6e9fc2a80e85fe48acd68818788723a30 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 18:28:53 +0200 Subject: [PATCH 056/224] Revert "fix: bug with not tracking limit of spawned players in custom roles (#585)" This reverts commit 7ea3cccc1ae54ae3277b6fa79c3777c49a7b4392. --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 67c3826408..ba6ecc95f8 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -924,7 +924,7 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) private void OnInternalSpawned(SpawnedEventArgs ev) { - if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance && (SpawnProperties.Limit == 0 || TrackedPlayers.Count < SpawnProperties.Limit)) + if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance) AddRole(ev.Player); } From aa4d63973bfef7cb88b33c87b70c106e9413dc93 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 18:29:12 +0200 Subject: [PATCH 057/224] Revert "feat: Adding base game All players spawned event for exiled (#558)" This reverts commit ac64e9220df9dc314c630ef3e23c0026c6c2b517. --- EXILED/Exiled.Events/Events.cs | 4 +--- EXILED/Exiled.Events/Handlers/Server.cs | 12 +----------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 79ee8b0c4d..103ba58829 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -73,7 +73,6 @@ public override void OnEnabled() Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; CharacterClassManager.OnRoundStarted += Handlers.Server.OnRoundStarted; - RoleAssigner.OnPlayersSpawned += Handlers.Server.OnAllPlayersSpawned; WaveManager.OnWaveSpawned += Handlers.Server.OnRespawnedTeam; InventorySystem.InventoryExtensions.OnItemAdded += Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved += Handlers.Player.OnItemRemoved; @@ -114,7 +113,6 @@ public override void OnDisabled() InventorySystem.InventoryExtensions.OnItemAdded -= Handlers.Player.OnItemAdded; InventorySystem.InventoryExtensions.OnItemRemoved -= Handlers.Player.OnItemRemoved; - RoleAssigner.OnPlayersSpawned -= Handlers.Server.OnAllPlayersSpawned; WaveManager.OnWaveSpawned -= Handlers.Server.OnRespawnedTeam; RagdollManager.OnRagdollSpawned -= Handlers.Internal.RagdollList.OnSpawnedRagdoll; RagdollManager.OnRagdollRemoved -= Handlers.Internal.RagdollList.OnRemovedRagdoll; @@ -163,4 +161,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/Handlers/Server.cs b/EXILED/Exiled.Events/Handlers/Server.cs index 80453625bb..7ddffb003a 100644 --- a/EXILED/Exiled.Events/Handlers/Server.cs +++ b/EXILED/Exiled.Events/Handlers/Server.cs @@ -32,11 +32,6 @@ 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. /// @@ -142,11 +137,6 @@ 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. /// @@ -255,4 +245,4 @@ public static class Server /// The instance. public static void OnCompletingObjective(CompletingObjectiveEventArgs ev) => CompletingObjective.InvokeSafely(ev); } -} +} \ No newline at end of file From 7c68ea72ea0d8f12994081ff84ad6347d9066c2d Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 18:40:37 +0200 Subject: [PATCH 058/224] Fix using --- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 2abfc50377..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; From baff9c5cad0650a4446d7cab1d6f3dd0f9e51b6b Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:44:45 +0300 Subject: [PATCH 059/224] feat: All players spawned2 (#597) * Update Events.cs * Update Server.cs --- EXILED/Exiled.Events/Events.cs | 3 ++- EXILED/Exiled.Events/Handlers/Server.cs | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 80173fa27a..b2c8e47e19 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -73,6 +73,7 @@ public override void OnEnabled() 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; @@ -112,7 +113,7 @@ public override void OnDisabled() 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; 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 +} From f1df1bd43671bf1072bf99e218f929db18399333 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:44:50 +0200 Subject: [PATCH 060/224] refactor: StoredComponent (#328) * StoredComponent * Fix BreakingChange * Fix new Log Warning --- EXILED/Exiled.API/Features/PrefabHelper.cs | 18 ++++++++++----- .../Handlers/Internal/ClientStarted.cs | 23 ++++++++++--------- 2 files changed, 24 insertions(+), 17 deletions(-) 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.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 From 4cf27fe93c7546b5ea1f73db1989bee71332dc4b Mon Sep 17 00:00:00 2001 From: Federico Cosma <61429263+FoxWorn3365@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:46:40 +0200 Subject: [PATCH 061/224] feat: `[EXILED::Loader]` Allow port-binded plugin loading (#89) * loader updoot & removed unused using * Update EXILED/Exiled.Loader/Loader.cs Suggestion #1 Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * Update EXILED/Exiled.Loader/Loader.cs Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * moved method private methods should be defined after public ones * updoot exiled kill sissues Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * updoot exiled kill sissues Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * updoot exiled kill sissues Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * updoot * fixed the infamous error * rollback - not same pr * fix Conflict from Merge branch 'dev' into pr/89 * Update Loader.cs --------- Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: Yamato --- EXILED/Exiled.Loader/Loader.cs | 78 +++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 29 deletions(-) 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 +} From b94f444c4e962b81961c1589707f1a9b5357da0e Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:47:06 +0300 Subject: [PATCH 062/224] feat: New Player Changed Room events (#557) * Create RoomChangedEventArgs.cs * Create ZoneChangedEventArgs.cs * Added Room / Zone Changed handlers * Create ChangedRoomZone.cs * Repeated referencehub references simplified * Simplify * Deleted ZoneChangedEventArgs.cs * Deleted ZoneChanged event handler * Deleted ChangedZone event codes * Update and rename ChangedRoomZone.cs to ChangedRoom.cs * More Comment * More Fast method * Micro Optimization --- .../EventArgs/Player/RoomChangedEventArgs.cs | 44 +++++++++ EXILED/Exiled.Events/Handlers/Player.cs | 11 +++ .../Patches/Events/Player/ChangedRoom.cs | 91 +++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs 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/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index b444aa4b4f..7037747e7e 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -494,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. /// @@ -810,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/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); + } + } +} From eb6ac982b0092ec7dce8c4306c217a2ae1a3c789 Mon Sep 17 00:00:00 2001 From: Valentin Arthur Thomas <64769541+warquys@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:47:20 +0200 Subject: [PATCH 063/224] feat: Add priority for event handlers to Exiled Events (#423) * Add priority for event handlers to Exiled Events * Protect inner event list from edition durring Invoke * remove comments * prevent null handler to be attached --- .gitignore | 5 +- EXILED/Exiled.Events/Features/Event.cs | 135 ++++++++++++++++++---- EXILED/Exiled.Events/Features/Event{T}.cs | 134 +++++++++++++++++---- 3 files changed, 229 insertions(+), 45 deletions(-) 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.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index 9885747c7e..fc5c0c4333 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -14,6 +14,7 @@ namespace Exiled.Events.Features using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; using MEC; + using PluginAPI.Roles; /// /// The custom delegate, with empty parameters. @@ -31,8 +32,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 +56,6 @@ public Event() EventsValue.Add(this); } - private event CustomEventHandler InnerEvent; - - private event CustomAsyncEventHandler InnerAsyncEvent; - /// /// Gets a of which contains all the instances. /// @@ -105,6 +114,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 +131,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 +153,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 +170,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 +193,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 +204,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 +214,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 +276,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}"); } } } From 27af6360f3eec2b9ffdef41a451432680a6a922c Mon Sep 17 00:00:00 2001 From: GoldenPig1205 <78902671+GoldenPig1205@users.noreply.github.com> Date: Fri, 1 Aug 2025 04:08:59 +0900 Subject: [PATCH 064/224] docs: Korean Exiled Docs Update (#563) Update README-KR.md --- .../documentation/localization/README-KR.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/documentation/localization/README-KR.md b/.github/documentation/localization/README-KR.md index a70baf4a5f..547670328e 100644 --- a/.github/documentation/localization/README-KR.md +++ b/.github/documentation/localization/README-KR.md @@ -11,14 +11,14 @@ -EXILED는 SCP: 비밀 연구소 서버들을 위한 고급 플러그인 프레임워크입니다. 개발자가 게임의 코드를 바꾸거나 자신만의 기능을 넣을 수 있는 이벤트 시스템을 제공하며, 모든 EXILED 이벤트들은 Harmony로 작성되어 직접적으로 서버의 어셈블리를 바꿀 필요가 없어, 2가지의 장점을 만들어냅니다. +EXILED는 SCP: 비밀 연구소 서버들을 위한 고급 플러그인 프레임워크입니다. 개발자가 게임의 코드를 바꾸거나 자신만의 기능을 넣을 수 있는 이벤트 시스템을 제공하며, 모든 EXILED 이벤트들은 Harmony로 작성되어 직접적으로 서버의 어셈블리를 바꿀 필요가 없다는 2가지의 장점이 있습니다. - 첫째로, 모든 프레임워크의 코드는 자유롭게 공유되거나 배포될 수 있어, 개발자들이 _어떻게_ 프레임워크가 작동하고 기능을 추가하고나 바꾸는 방법을 배울 수 있습니다. -- 둘째로, 프레임워크가 관련된 모든 코드가 서버 어셈블리 밖에서 작동되기 때문에, 만약 작은 업데이트가 존재하더라도, 프레임워크에는 아주 작은 영향을 미치게 됩니다. 이로 하여금 계속되는 업데이트와의 호환성이 (거의) 보장되며, 업데이트를 _필요할 때_ 할 수 있습니다. +- 둘째로, 프레임워크가 관련된 모든 코드가 서버 어셈블리 밖에서 작동되기 때문에, 만약 작은 업데이트가 존재하더라도, 프레임워크에는 아주 작은 영향을 미치게 됩니다. 이로 하여금 계속되는 업데이트와의 호환성이 (거의) 보장되며, 업데이트를 _필요할 때_만 해도 됩니다. # EXILED 설치하기 -EXILED의 설치 과정은 상당히 간단합니다. 노스우드의 Plugin API을 이용해 프레임워크를 불러오며, 이는 릴리즈 파일 `Exiled.tar.gz` 안에 2개의 폴더가 있는 이유가 됩니다. `SCP Secret Laboratory` 폴더는 `EXILED` 폴더에 있는 EXILED의 모든 기능을 불러오는데 필요한 파일들을 담고 있습니다. 이제 이 두 폴더를 (아래에 설명된) 위치로 옮기면, 짜잔! EXILED 설치 완료! +EXILED의 설치 과정은 상당히 간단합니다. 노스우드의 Plugin API을 이용해 프레임워크를 불러오며, 이는 릴리즈 파일 `Exiled.tar.gz` 안에 2개의 폴더가 있는 이유입니다. `SCP Secret Laboratory` 폴더는 `EXILED` 폴더에 있는 EXILED의 모든 기능을 불러오는데 필요한 파일들을 담고 있습니다. 이제 이 두 폴더를 (아래에 설명된) 위치로 옮기면, 짜잔! EXILED 설치가 완료되었습니다! 만약 설치 프로그램을 사용한다면, 제대로 실행할 시 모든 EXILED의 기능을 자동으로 설치할 것입니다. @@ -30,7 +30,7 @@ EXILED의 설치 과정은 상당히 간단합니다. 노스우드의 Plugin API - **[여기에서](https://github.com/ExMod-Team/EXILED/releases) `Exiled.Installer-Win.exe`** 를 다운로드합니다. (Assets 클릭 -> Exiled.Installer-Win.exe 클릭) - 서버와 같은 폴더에 프로그램을 위치시킵니다. (서버를 다운로드 받지 않았다면 설치하세요.) -- **`Exiled.Installer.exe`** 를 실행하거나 프리릴리즈 버전을 설치하려면 **이 [.bat 파일](https://www.dropbox.com/scl/fi/7yh0r3q0vdn6ic4rhuu3l/install-prerelease.bat?rlkey=99fwjbwy1xg61qgtak0qzb9rd&st=8xs4xks8&dl=1)을 받아** 서버 폴더에 넣고 실행합니다. +- **`Exiled.Installer.exe`** 를 실행하거나 프리릴리즈(사전 출시) 버전을 설치하려면 **이 [.bat 파일](https://www.dropbox.com/scl/fi/7yh0r3q0vdn6ic4rhuu3l/install-prerelease.bat?rlkey=99fwjbwy1xg61qgtak0qzb9rd&st=8xs4xks8&dl=1)을 받아** 서버 폴더에 넣고 실행합니다. - 플러그인을 다운로드하고 설치하려면, 아래 [플러그인 설치](#플러그인-설치) 문단을 참고하세요. **주의사항:** EXILED를 원격 서버에 설치하려고 한다면, .exe 파일을 실행할 때 SCP: 비밀 연구소 서버를 실행할 유저와 같은 유저인지, 또는 관리자 권한을 가진 유저인지 확인해주세요. @@ -47,7 +47,7 @@ EXILED의 설치 과정은 상당히 간단합니다. 노스우드의 Plugin API ### 플러그인 설치 -자, 이제 모든 설치가 완료되었습니다! EXILED는 이제 설치되었고 다음 서버 구동시 활성화 될 것입니다. EXILED 자체로는 아무것도 하지 않습니다. **[저희의 디스코드 서버](https://discord.gg/PyUkWTg)** 에서 새롭고 재미있는 플러그인을 찾아보세요! +자, 이제 모든 설치가 완료되었습니다! EXILED는 이제 설치되었고 다음 서버 구동시 활성화 될 것입니다. EXILED 자체로는 어떠한 일도 일어나지 않습니다. **[저희의 디스코드 서버](https://discord.gg/PyUkWTg)** 에서 새롭고 재미있는 플러그인을 찾아보세요! - 플러그인을 설치하려면: - [_플러그인의_ 릴리즈 페이지](https://i.imgur.com/u34wgPD.jpg)에서 플러그인을 다운로드하세요. (**무조건 `.dll` 파일 형식이어야 합니다!**) @@ -60,8 +60,8 @@ EXILED의 설치 과정은 상당히 간단합니다. 노스우드의 Plugin API **주의사항**: 설치 프로그램을 사용하기 전에, SCP: 비밀 연구소 서버를 실행할 유저와 같은 유저인지, 또는 `root` 유저인지 확인해주세요. - **`Exiled.Installer-Linux`** 를 **[여기서](https://github.com/ExMod-Team/EXILED/releases)** 다운로드 하세요. (Assets 클릭 -> 설치 프로그램 다운로드) -- **`./Exiled.Installer-Linux --path /path/to/server`** 를 입력해 설치하거나, 파일을 서버 폴더 안에 위치시키고 터미널을 이동시킨 다음 (`cd`) **`./Exiled.Installer-Linux`** 를 입력하세요. -- 프리릴리즈 버전을 원한다면, 실행 시 **`--pre-releases`** 를 추가하세요. 예시: **`./Exiled.Installer-Linux /home/scp/server --pre-releases`** +- **`./Exiled.Installer-Linux --path /path/to/server`** 를 입력해 설치하거나, 파일을 서버 폴더 안에 위치시키고 터미널(콘솔)의 경로를 이동시킨 다음 (`cd`) **`./Exiled.Installer-Linux`** 를 입력하세요. +- 프리릴리즈(사전 출시) 버전을 원한다면, 실행 시 **`--pre-releases`** 를 추가하세요. 예시: **`./Exiled.Installer-Linux /home/scp/server --pre-releases`** - 또 다른 예시: 만약 `Exiled.Installer-Linux`를 서버 폴더 안에 위치시켰다면: **`/home/scp/server/Exiled.Installer-Linux --pre-releases`** 를 입력하세요. - 플러그인을 다운로드하고 설치하려면 [플러그인 설치](#플러그인-설치-1) 문단을 참고해주세요. @@ -91,16 +91,16 @@ EXILED 자체도 몇 가지 설정을 가지고 있습니다. 이 설정들은 # 개발자들을 위한 정보 -만약 EXILED 플러그인을 만들고 싶다면, 사실 간단합니다. 튜토리얼을 원한다면 [여기 (영어)](GettingStarted.md)를 읽어주세요. +만약 EXILED 플러그인을 만들고 싶다면, 간단한 방법이 있습니다. 튜토리얼을 원한다면 [여기 (영어)](GettingStarted.md)를 읽어주세요. -좀 더 상세하고 자주 업데이트 되는 튜토리얼을 원한다면, [EXILED 웹사이트](https://exmod-team.github.io/EXILED/)를 방문해보세요. +또는 조금 더 상세하고 자주 업데이트 되는 튜토리얼을 원한다면, [EXILED 웹사이트](https://exmod-team.github.io/EXILED/)를 방문해보세요. 하지만 플러그인을 만들기 전에, 몇 가지 주의사항을 알아두세요: - 당신의 플러그인은 **무조건** `Exiled.API.Features.Plugin<>`을 상속받는 클래스를 가지고 있어야 합니다. 없다면 EXILED 실행 시 플러그인이 작동되지 않습니다. - 플러그인이 실행되면, `OnEnabled()` 메서드가 **바로** 실행됩니다. 다른 플러그인이 불러와질 때까지 기다리지도 않고, 서버 시작 절차가 끝날 때가지 기다리지도 않습니다. **_아무것도 기다리지 않아요._** `OnEnabled()` 메소드을 구현할 때, 아직 설정이 되지 않은 것들, 예를 들어 `ServerConsole.Port`, 혹은 `PlayerManager.localPlayer` 을 사용하고 있지는 않은지 확인하세요. - 만약 아직 설정이 되지 않은 것들을 빨리 사용하고 싶다면, `WaitingForPlayers` 이벤트를 기다리거나, 더 빨리, 최대한 빨리 사용하고 싶다면 코드를 `while(!x)` 안에 접근하고 싶은 것이 `null`이 아닌지 확인하는 코드와 함께 넣어주세요. -- EXILED는 플러그인 어셈블리들을 서버 동작 중 동적으로 다시 불러올 수 있습니다. 플러그인을 업데이트하고 싶다면, 서버를 재시작할 필요가 없습니다. 하지만, 플러그인을 다시 불러온다면, 플러그인이 리로드를 지원하는지 확인하세요. 지원하지 않은 상태로 리로드를 시도하면, 아주, 매우, 안 좋은 시간을 보내게 될 겁니다. `동적 리로드` 문단을 읽어 보세요. +- EXILED는 플러그인 어셈블리들을 서버 동작 중 동적으로 다시 불러올 수 있습니다. 플러그인을 업데이트하고 싶다면, 서버를 재시작할 필요가 없습니다. 하지만, 플러그인을 다시 불러온다면, 플러그인이 리로드를 지원하는지 확인하세요. 지원하지 않은 상태로 리로드를 시도하면, 매우 끔찍한 시간을 보내게 될 겁니다. `동적 리로드` 문단을 읽어 보세요. - OnUpdate, OnFixedUpdate 이나 OnLateUpdate는 EXILED에 **_존재하지 않습니다_**. 코드를 그 정도로 많이 실행시키고 싶다면, MEC 코루틴을 사용하여 1 프레임, 0.01초 기다리는 코루틴을 사용하거나 Timing 레이어 (Timing.FixedUpdate 등) 를 사용하세요. ### MEC 코루틴 @@ -123,34 +123,34 @@ public void SomeMethod() public IEnumerator MyCoroutine() { - for (;;) // 무한 반복 + for (;;) // 무한 반복 (또는 while (true)) { Log.Info("무⭐한⭐반⭐복!"); // 서버 로그를 생성합니다. - yield return Timing.WaitForSeconds(5f); //코루틴에게 5초를 기다리라고 지시합니다. + yield return Timing.WaitForSeconds(5f); //코루틴에게 5초를 대기하라고 지시합니다. } } ``` -MEC과 익숙치 않거나, 더 배우고 싶거나, 조언을 좀 받고 싶나, 도움이 필요하다면 구글링을 좀 하거나 디스코드에서 물어보는 것이 **_매우_** 추천됩니다. 질문들은 얼마나 '수준 낮은지' 와 상관없이, 플러그인 개발자분들에게 추진력을 실어주기 위해 항상 최대한 호의적이고 깔끔하게 답변될 것입니다. 더 좋은 코드는 모두에게 좋은 것이니까요! +MEC과 익숙치 않거나, 더 배우고 싶거나, 조언을 좀 받고 싶거나, 도움이 필요하다면, 구글링을 조금 해보거나 디스코드에서 물어보는 것을 **_매우_** 추천합니다. 질문들은 얼마나 '수준 낮은지' 와 상관없이, 플러그인 개발자분들에게 추진력을 실어주기 위해 항상 최대한 호의적이고 깔끔하게 답변될 것입니다. 더 좋은 코드는 모두에게 좋은 것이니까요! ### 동적 리로드 -EXILED는 플러그인 어셈블리를 서버를 재시작하지 않고도 동적으로 재시작 할 수 있는 프레임워크입니다. -예를 들어, 만약 `Exiled.Events` 1개의 플러그인을 서버를 시작하고, 새로운 플러그인을 추가하고 실행하고 싶다면, 서버를 재시작 할 필요가 없습니다. `reload plugins`을 RA나 서버 콘솔에 입력해 모든 EXILED 플러그인-전에 없던 플러그인들도 리로딩할 수 있습니다. +EXILED는 플러그인 어셈블리를 서버를 재시작하지 않고도 동적으로 재시작할 수 있는 프레임워크입니다. +예를 들어, 만약 `Exiled.Events` 1개의 플러그인을 서버를 시작하고, 새로운 플러그인을 추가하고 실행하고 싶다면, 서버를 재시작할 필요가 없습니다. `reload plugins`을 RA나 서버 콘솔에 입력해 모든 EXILED 플러그인(전에 없던 플러그인들도 포함)들을 리로딩할 수 있습니다. 이것은 또한 플러그인을 완전히 서버를 재시작하지 않다고 *업데이트*를 할 수 있다는 것을 의미합니다. 하지만 이를 지원하기 위해, 플러그인 개발자들이 따라야 할 몇 가지 규칙이 있습니다. **_서버 주인은_** - 플러그인을 업데이트하고 있다면, 플러그인의 어셈블리 이름이 현재와 다른지 확인하세요. 플러그인 개발자가 동적 리로딩을 염두에 두고 플러그인을 개발해야 했으며, 파일 이름을 바꾸는 것 만으로는 충분하지 않습니다. -- 만약 플러그인이 동적 리로딩을 지원한다면, 새로운 플러그인을 "Plugins" 폴더에 넣을 때, 전에 있던 플러그인을 먼저 지워야 합니다. 그렇지 않으면, 안 좋은 일이 많이 일어날 수 있습니다. +- 만약 플러그인이 동적 리로딩을 지원한다면, 새로운 플러그인을 "Plugins" 폴더에 넣을 때, 전에 있던 플러그인을 먼저 지워야 합니다. 그렇지 않으면, 좋지 않은 일이 많이 일어날 수 있습니다. - 플러그인을 동적으로 리로딩하는데 생기는 모든 책임은 모두 플러그인 개발자 혹은 서버 주인에게 있습니다. EXILED가 동적 리로드를 지원하는데, 문제가 생기는 방법은 플러그인 개발자나 서버 주인이 무언가를 잘못하는 방법 밖에는 없습니다. EXILED 개발자들에게 동적 리로드로 버그를 신고하기 전에 둘 모두에게 모든 것이 제대로 동작되었는지 확인하세요. **_개발자는_** -- 동적 리로딩을 지원하는 플러그인을 만들 때, 플러그인이 걸려있던 모든 이벤트들을 비활성화되거나 리로딩 할 때 해제되어야 합니다. +- 동적 리로딩을 지원하는 플러그인을 만들 때, 플러그인이 걸려있던 모든 이벤트들을 비활성화되거나 리로딩할 때 해제되어야 합니다. - Harmony 패치가 있는 플러그인이라면 Harmony 인스턴스를 만들 때 이름을 일종의 항상 바뀌는 변수로 만들어야 합니다. 또한 플러그인이 비활성화되거나 리로딩 될 때 Harmony 패치를 `UnPatchAll()` 메소드로 해제해야 합니다. -- `OnEnabled()` 메소드에서 시작된 모든 코루틴들은 플러그인이 비활성화되거나 리로딩 될 때 중지되어야 합니다. +- `OnEnabled()` 메소드에서 시작된 모든 코루틴들은 플러그인이 비활성화되거나 리로딩될 때 중지되어야 합니다. 위의 모든 것들은 `OnReloaded()` 또는 `OnDisabled()` 메소드에서 수행될 수 있습니다. EXILED가 플러그인을 리로드할 때, `OnDisabled()`가 호출되고 `OnReloaded()`가 호출됩니다. 그리고 새로운 어셈블리를 로드하고 `OnEnabled()`가 호출됩니다. @@ -159,6 +159,6 @@ _새로운_ 어셈블리들이라는 것에 주목해주세요. 만약 똑같은 이 때문에, 동적 리로딩을 지원하는 플러그인을 만들 때, **_무조건_** 이러한 규칙을 따라야 합니다. 만약 이러한 규칙을 따르지 않는다면, 디스코드 서버에서 삭제될 수 있습니다. -모든 플러그인이 동적 리로딩을 지원하지 않아도 됩니다. 만약 동적 리로딩을 지원하지 않기로 생각했다면, 진짜 괜찮아요. :) -플러그인의 어셈블리 버전을 바꾸는 것을 피하시고, 서버 주인분들에게 플러그인을 업데이트하기 위해 서버를 재시작하라고 요청하세요. +모든 플러그인이 동적 리로딩을 지원하지 않아도 됩니다. 동적 리로딩을 지원하지 않는 것도 나쁘지 않습니다. :) +플러그인의 어셈블리 버전을 바꾸는 것을 피하시고, 서버 주인분들에게 플러그인을 업데이트하기 위해 서버를 재시작하라고 당부해두세요. From f678399cfb7d1a14dc4904db71046e854ad44841 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 21:21:20 +0200 Subject: [PATCH 065/224] Fix again ChangingKnobSetting --- EXILED/Exiled.Events/Patches/Events/Scp914/InteractingEvents.cs | 1 + 1 file changed, 1 insertion(+) 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 From 85b8e30b62e0c02323109a2c848ec1c0db9b8c1d Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 21:54:58 +0200 Subject: [PATCH 066/224] Remove unused using directives in Event.cs --- EXILED/Exiled.Events/Features/Event.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/EXILED/Exiled.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index fc5c0c4333..88b22843f0 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -9,12 +9,10 @@ namespace Exiled.Events.Features { using System; using System.Collections.Generic; - using System.Linq; using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; using MEC; - using PluginAPI.Roles; /// /// The custom delegate, with empty parameters. From 85dc42543f38e0fed664e86dc426d35c248c713c Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:47:44 +0300 Subject: [PATCH 067/224] feat: Item::Inspected Item::Inspecting & Scp939::PlacingMimicPoint (#473) * feat: a bunch of new evs * revert Events.cs changes * docs: comments for transpilers * feat: support for all items * feat: mhid support * docs: fetch documentation * fix: fix InspectedItemEventArgs.cs and docs update --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../EventArgs/Item/InspectedItemEventArgs.cs | 35 ++ .../EventArgs/Item/InspectingItemEventArgs.cs | 41 +++ .../Scp939/PlacingMimicPointEventArgs.cs | 48 +++ EXILED/Exiled.Events/Handlers/Item.cs | 22 ++ EXILED/Exiled.Events/Handlers/Scp939.cs | 11 + .../Patches/Events/Item/Inspect.cs | 319 ++++++++++++++++++ .../Events/Scp939/PlacingMimicPoint.cs | 84 +++++ 7 files changed, 560 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Item/InspectedItemEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Item/InspectingItemEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp939/PlacingMimicPointEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Item/Inspect.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp939/PlacingMimicPoint.cs 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/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/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index af9dea76b5..4020038448 100644 --- a/EXILED/Exiled.Events/Handlers/Item.cs +++ b/EXILED/Exiled.Events/Handlers/Item.cs @@ -63,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. @@ -129,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/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/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/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 From af83ed7dd1f029135126fc4aa259d2a45363684d Mon Sep 17 00:00:00 2001 From: R2kip <56793105+R2kip@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:21:38 +0300 Subject: [PATCH 068/224] feat: Add PermissionsPolicy and RequireAllPermissions in Door (#523) * LabAPI Update (#435) * No error for build * Fix Exiled * Fix RoundEnding * Now is fucking separated by default * CustomSubtitles * Missing IsCustomAnnouncement * Fix 3 patch & removal of CustomHealthStat * Fix UsingAndCancellingItemUse * Fix TogglingFlashlight * La Flemme ? * La flemme 2 * Fix ReservedSlotPatch * Fix PickingUpItem(PickingUpAmmo) * Fix Hurt event * Fix FlippingCoin & EscapingPocketDimension * Fix FailingEscapePocketDimension * EscapingAndEscaped fix patch * Fix UpgradingPlayer patch * Fix BeingObserved * Fix PlacingTantrum * Fix FinishingRecall * ChangingCamera (Not working) * putting again the event than i was lazzy to fix * Rework InteractingDoor Patch missing removing the Prefix part lol & SkillIssueFix * Remove useless using and warning disable * OUPS * if stupidity was me it's here (i am stupid) * Fixing Noclip with breakingchange * CustomHumeShieldStat * Fix InteractingGenerator * Fix ChangingCamera * Not finish * InteractingDoor swap CanInteract & IsAllowed back to normal * Player::Gravity * Gravity in wrong place * IHumeShieldRole for Human * Fix BlastDoors & add OpenBlastDoor * Fix SpawningRagdoll Scale & missing argument when modify * Now fix in LabAPI only * we will keep the funny Ragdoll scale * Fix Merge & Split Command * new workflow for LabAPI * LabAPI Update 14.0.3 (#439) * No error for build * Fix Exiled * Fix RoundEnding * Now is fucking separated by default * CustomSubtitles * Missing IsCustomAnnouncement * Fix 3 patch & removal of CustomHealthStat * Fix UsingAndCancellingItemUse * Fix TogglingFlashlight * La Flemme ? * La flemme 2 * Fix ReservedSlotPatch * Fix PickingUpItem(PickingUpAmmo) * Fix Hurt event * Fix FlippingCoin & EscapingPocketDimension * Fix FailingEscapePocketDimension * EscapingAndEscaped fix patch * Fix UpgradingPlayer patch * Fix BeingObserved * Fix PlacingTantrum * Fix FinishingRecall * ChangingCamera (Not working) * putting again the event than i was lazzy to fix * Rework InteractingDoor Patch missing removing the Prefix part lol & SkillIssueFix * Remove useless using and warning disable * OUPS * if stupidity was me it's here (i am stupid) * Fixing Noclip with breakingchange * CustomHumeShieldStat * Fix InteractingGenerator * Fix ChangingCamera * Not finish * InteractingDoor swap CanInteract & IsAllowed back to normal * Player::Gravity * Gravity in wrong place * IHumeShieldRole for Human * Fix BlastDoors & add OpenBlastDoor * Fix SpawningRagdoll Scale & missing argument when modify * Now fix in LabAPI only * we will keep the funny Ragdoll scale * Fix Merge & Split Command * new workflow for LabAPI * NW_Documentation * SCP:SL LabAPI 14.0.3 * Fix One IL Error Patch (2 remain from previous update) * use Specific build for LabAPI * Miss updating this file * 9.6.0-beta3 * this will need to be revert * push_nuget not working for LabAPI * Fixing RoundEnd Patch * Adding CustomRole::Gravity * Remove Testing Log * fix: Command events fix (#444) command fixes * fix: Fixing UnlockingGenerator * Fix RoundEnd completelly * fix: Hurting event NRE fix (#443) fix: one day i'll kill person who made this Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * 9.6.0-beta4 * this will required to be revert * fix: InteractingDoorEvent::CanInteract (#463) * Fix InteractingDoorEvent::CanInteract * Oupsy * Fixed Error & InteractingScp330 IL Code * TODO: Fix THE CRASH ISSUE * Fix Consumed Event - doubt * Fix & Small change on UsingAndCancellingItemUse * Fix an issue where HashSetPool was never return * Fix IL CODE error & remove debug * Add warning in case of missing PrefabType * Capybara Toy * remove using * Fix SpawningRagdoll not setting NetworkInfo * Fix Ragdoll Scale & ConsumingEvent * Missing NetworkInfo.Scale Argument * Fix: Door.Get(GameObject) * TantrumHazard is always null * 9.6.0-beta5 * Update EXILED_DLL_ARCHIVER_URL for LabAPI * fix: use basegame validation for CustomInfo (#452) * Validate using basegame * Update EXILED/Exiled.API/Features/Player.cs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * feat: Custom limits for armor & virtual dmg for customweapon (#477) * fix: activating warhead panel event fix (#504) fix: fix for activating warhead panel * Fix Error (from Conflict) * Fix doc error * Fix few error * refactor!: update to 14.1 (#508) * refactor!: update to 14.1 * fix: few patches fix * feat: new custom keycard logic * fix: ActivatingWarheadPanel event * fix: checkpoint sequence time * fix: Shot event * fix: DroppingCandy event * Fix last Error on Release Build * Update new PrefabType * fix: AnnouncingScpTermination event * fix: SpawningItem event error (require testing) * fix: Shot.cs fix * analysis error * Fix error * Fix build error * NW_Documentation * fix: announcing wave entrance * Reference new Room * Fix Build error * fix: fixes pickingup event * Nameless Skill Issue * fix: locker patch * fix: spawning room connector * CameraToy * Get CameraToy * Finish InteractableToy & fix doc * fix Error for build * Remove todo * fix: generator events * ElevatorType.ServerRoom * Fix a 2 hours loss * Fix NW moment * fix: interacting generator * fix: interacting locker event * fix: missing obstacle shot * fix: KeycardInteracting again * Fix SendingCassieMessage * fix: temp fix interacting door (need to refactor) * refactor: change the property * fix doc * extension for Scp127 & ServerRoom * FirearmType.Scp127 * AdminToyList * fix: camera weren't added * new CameraType * wrong doc * fix: interacting door event (improved) * Invalid Camera * Better doc * fix: interacting door (bolton issue) * FixDamagingWindow index * RemoveBreakingChange * Fix FailingEscapePocketDimensionEventArgs --------- Co-authored-by: Bolton <48883340+BoltonDev@users.noreply.github.com> Co-authored-by: VALERA771 Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> * 9.6.0-beta6 * SNOWBALL DO NOT EXIST * fix: ChangingCamera event (#511) * fix: useless fix now & prevent crash (#512) * fix: EndingRound event IsAllowed (#513) fix: EndingRound event isallowed * bump: 9.6.0-beta7 * fix: few fixes (#514) * fix: keycard interacting * fix: EscapingEvent IsAllowed * fix: command type invalid * fix: landing event * fix: EscapingPocketDimension event * fix: Handcuffing / RemovingHandcuff events * fix: sentvalidcommand event * bump: 9.6.0-beta8 * fix: fix AutoUpdater Directory finder (#515) * Update Capybara.cs * Revert "Update Capybara.cs" This reverts commit 6ea4789dc4d8cb8bb7f429281b930133bc9be62c. * fix autoupdater * ok now its fixed 100% * fix: custom weapons not doing default damage when set to below 0 (#518) * fix custom weapons not doing default damage when set to below 0 * make default damage -1 * Add PermissionsPolicy and RequireAllPermissions in Door * Workflow fix (#2) * v9.5.2 * Update release.yml * Update main.yml * update nuget * update docs * update dev * Workflow fix (#3) * v9.5.2 * Update release.yml * Update main.yml * update nuget * update docs * update dev * Fixing Error & Removing Property that will not be implmented * EffectType.Scp1344Detected * Update labapi.yml * Update labapi.yml * TextToy Implementation API * chore: remove hub (doesn't exist anymore) * fix: update exmod -> exslmod & bump: 9.6.0 * fix: missing some change * missing hub * fix: wrong repo id * fix: missing log warn * feat: Item::Inspected Item::Inspecting & Scp939::PlacingMimicPoint (#473) * feat: a bunch of new evs * revert Events.cs changes * docs: comments for transpilers * feat: support for all items * feat: mhid support * docs: fetch documentation * fix: fix InspectedItemEventArgs.cs and docs update --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Delete .github/workflows/labapi.yml * revert bad stuff again --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: Yamato Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> Co-authored-by: SlejmUr Co-authored-by: Bolton <48883340+BoltonDev@users.noreply.github.com> Co-authored-by: VALERA771 Co-authored-by: Mike <146554836+MikeSus1@users.noreply.github.com> Co-authored-by: TtroubleTT <121741230+TtroubleTT@users.noreply.github.com> --- EXILED/Exiled.API/Features/Doors/Door.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 22b0915132..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. /// From d44578ad73faeb76112395da73caa10a088e5a6d Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 31 Jul 2025 22:29:19 +0200 Subject: [PATCH 069/224] Remove breaking change --- .../DamageHandlers/GenericDamageHandler.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index 4d74256cca..1505ccc468 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -17,6 +17,7 @@ namespace Exiled.API.Features.DamageHandlers using PlayerRoles.PlayableScps.Scp939; using PlayerStatsSystem; + using System; using UnityEngine; /// @@ -210,6 +211,22 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage } } + /// + /// Initializes a new instance of the class. + /// Transform input data to custom generic handler. + /// + /// Current player (Target). + /// Attacker. + /// Damage quantity. + /// Damage type. + /// Custom cassie announcment. + /// Text to provide to player death screen. + [Obsolete("This constructor will be deleted in Exiled 10")] + public GenericDamageHandler(Player player, Player attacker, float damage, DamageType damageType, DamageHandlerBase.CassieAnnouncement cassieAnnouncement, string damageText) + : this(player, attacker, damage, damageType, cassieAnnouncement, damageText, false) + { + } + /// /// Gets or sets a custom base. /// From 52a72edf263794255880ea3fbad2141dc025cbd1 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 00:35:09 +0200 Subject: [PATCH 070/224] 9.7.0-rc.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 02d86cf5cc..760aff52d3 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.3 + 9.7.0-rc.1 false From 97f4c03dc70897690eee938ea756655b3c921a0d Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 00:40:42 +0200 Subject: [PATCH 071/224] Fix error --- .../Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs | 3 ++- EXILED/Exiled.API/Features/Player.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index 1505ccc468..95ebdf36c0 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -7,6 +7,8 @@ namespace Exiled.API.Features.DamageHandlers { + using System; + using Enums; using Footprinting; @@ -17,7 +19,6 @@ namespace Exiled.API.Features.DamageHandlers using PlayerRoles.PlayableScps.Scp939; using PlayerStatsSystem; - using System; using UnityEngine; /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 1689744773..16b47abd7c 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2150,7 +2150,7 @@ public void Hurt(Player attacker, float amount, DamageType damageType = DamageTy /// 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, CassieAnnouncement cassieAnnouncement, string deathText) => - Hurt(new GenericDamageHandler(this, attacker, amount, damageType, cassieAnnouncement, deathText)); + Hurt(new GenericDamageHandler(this, attacker, amount, damageType, cassieAnnouncement, deathText, false)); /// /// Hurts the player. From 8a5ba3c3dbfc7a8f3440d7d5358b7f2eecaf4f4a Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 22:49:42 +0200 Subject: [PATCH 072/224] Revert "Partially Revert "fix: Scale fix (#571)"" This reverts commit 151bed1ce903a66454a063875437c2e0b81eec9f. --- EXILED/Exiled.API/Features/Player.cs | 46 ++++++++++------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 16b47abd7c..7c43fc9b79 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -699,7 +699,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public Vector3 Scale { get => ReferenceHub.transform.localScale; - set => SetScale(value, List); + set => SetScale(value); } /// @@ -2065,6 +2065,16 @@ public void Disconnect(string reason = null) => /// public void ResetStamina() => Stamina = StaminaStat.MaxValue; + /// + /// Sets the scale of a player on the server side. + /// + /// The scale to set. + public void SetScale(Vector3 scale) + { + ReferenceHub.transform.localScale = scale; + new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToAuthenticated(); + } + /// /// Sets the scale of a player on the server side. /// @@ -2072,20 +2082,8 @@ public void Disconnect(string reason = null) => /// Who should see the updated scale. public void SetScale(Vector3 scale, IEnumerable viewers) { - if (scale == Scale) - return; - - try - { - ReferenceHub.transform.localScale = scale; - - foreach (Player target in viewers) - Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - } - catch (Exception exception) - { - Log.Error($"{nameof(SetScale)} error: {exception}"); - } + ReferenceHub.transform.localScale = scale; + new SyncedScaleMessages.ScaleMessage(scale, ReferenceHub).SendToHubsConditionally(x => x != null && viewers.Contains(Get(x))); } /// @@ -2095,21 +2093,9 @@ public void SetScale(Vector3 scale, IEnumerable viewers) /// Who should see the fake scale. public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) { - Vector3 currentScale = Scale; - - try - { - ReferenceHub.transform.localScale = fakeScale; - - foreach (Player target in viewers) - Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - - ReferenceHub.transform.localScale = currentScale; - } - catch (Exception ex) - { - Log.Error($"{nameof(SetFakeScale)}: {ex}"); - } + SyncedScaleMessages.ScaleMessage scaleMessage = new(fakeScale, ReferenceHub); + foreach (Player player in viewers) + player.Connection.Send(scaleMessage, 0); } /// From 71087a958a859735b4e47a4b709aeb232581056b Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 23:20:22 +0200 Subject: [PATCH 073/224] Revert "SpawnReason.CustomRole" This reverts commit 3c7ce4d9405307fc79a6d4d8363b33b6f38442f9. --- EXILED/Exiled.API/Enums/SpawnReason.cs | 5 ----- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/EXILED/Exiled.API/Enums/SpawnReason.cs b/EXILED/Exiled.API/Enums/SpawnReason.cs index 3bde3457df..a1849ba8a6 100644 --- a/EXILED/Exiled.API/Enums/SpawnReason.cs +++ b/EXILED/Exiled.API/Enums/SpawnReason.cs @@ -66,10 +66,5 @@ public enum SpawnReason : byte // TOTO: Remove this file and use Basegame /// 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.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index db5e599cd3..b1e70e6b2d 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -519,16 +519,16 @@ public virtual void AddRole(Player player) if (KeepPositionOnSpawn) { if (KeepInventoryOnSpawn) - player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.None); + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None); else - player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.AssignInventory); + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory); } else { if (KeepInventoryOnSpawn && player.IsAlive) - player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.UseSpawnpoint); + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint); else - player.Role.Set(Role, SpawnReason.CustomRole, RoleSpawnFlags.All); + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All); } } @@ -952,7 +952,7 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) private void OnInternalChangingRole(ChangingRoleEventArgs ev) { - 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))) + if (ev.IsAllowed && ev.Reason != SpawnReason.Destroyed && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole))) RemoveRole(ev.Player); } From 913509fffb21f33c100b8a73b82ec56a6f4d6886 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 23:20:29 +0200 Subject: [PATCH 074/224] Revert "Fix CustomRole Issue not working when using OnSpawned" This reverts commit 28597de03674add9cb84b4d29d5f4d77ba9f043e. --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index b1e70e6b2d..4838224e84 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -512,7 +512,6 @@ public virtual void AddRole(Player player) { Log.Debug($"{Name}: Adding role to {player.Nickname}."); player.UniqueRole = Name; - TrackedPlayers.Add(player); if (Role != RoleTypeId.None) { @@ -533,6 +532,7 @@ public virtual void AddRole(Player player) } player.UniqueRole = Name; + TrackedPlayers.Add(player); Timing.CallDelayed( AddRoleDelay, From dbc5df08cefa1cc7bb92a6c5b470baa79d641bd4 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 1 Aug 2025 23:57:17 +0200 Subject: [PATCH 075/224] Update Error and TT file --- EXILED/Exiled.API/Enums/EffectType.cs | 5 + EXILED/Exiled.API/Enums/PrefabType.cs | 9 ++ .../Extensions/EffectTypeExtension.cs | 1 + EXILED/Exiled.API/Features/Map.cs | 8 +- EXILED/Exiled.API/Features/Player.cs | 6 +- EXILED/Exiled.API/Features/Toys/Capybara.cs | 4 +- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- .../SCPSLRessources/NW_Documentation.md | 121 ++++++++++++------ 8 files changed, 106 insertions(+), 50 deletions(-) diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index b32ba684d8..f80ad50b0c 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -288,5 +288,10 @@ public enum EffectType /// . /// Scp1344Detected, + + /// + /// . + /// + Scp1576, } } diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index f34093aba8..243470f07d 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -372,5 +372,14 @@ public enum PrefabType [Prefab(162530276, "TextToy")] TextToy, + + [Prefab(4046276968, "Spawnable Unsecured Pryable GateDoor")] + SpawnableUnsecuredPryableGateDoor, + + [Prefab(2332883846, "CullableParentToy")] + CullableParentToy, + + [Prefab(3938583646, "WaypointToy")] + WaypointToy, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index 6f038fd761..d70ee9839b 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -76,6 +76,7 @@ public static class EffectTypeExtension { EffectType.PitDeath, typeof(PitDeath) }, { EffectType.Blurred, typeof(Blurred) }, { EffectType.Scp1344Detected, typeof(Scp1344Detected) }, + { EffectType.Scp1576, typeof(Scp1576) }, #pragma warning disable CS0618 { EffectType.Marshmallow, typeof(MarshmallowEffect) }, { EffectType.BecomingFlamingo, typeof(BecomingFlamingo) }, diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 7bd9a19193..266d04c4d1 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -58,7 +58,7 @@ public static class Map /// Gets a value indicating whether decontamination phase is in the light containment zone. /// public static DecontaminationState DecontaminationState => - DecontaminationController.Singleton.NetworkDecontaminationOverride is DecontaminationController.DecontaminationStatus.Disabled ? + DecontaminationController.Singleton.DecontaminationOverride is DecontaminationController.DecontaminationStatus.Disabled ? DecontaminationState.Disabled : (DecontaminationState)DecontaminationController.Singleton._nextPhase; /// @@ -92,9 +92,9 @@ public static int Seed /// public static bool IsDecontaminationEnabled { - get => DecontaminationController.Singleton.NetworkDecontaminationOverride == DecontaminationController.DecontaminationStatus.None; + get => DecontaminationController.Singleton.DecontaminationOverride == DecontaminationController.DecontaminationStatus.None; set => - DecontaminationController.Singleton.NetworkDecontaminationOverride = value + DecontaminationController.Singleton.DecontaminationOverride = value ? DecontaminationController.DecontaminationStatus.None : DecontaminationController.DecontaminationStatus.Disabled; } @@ -182,7 +182,7 @@ public static void ShowHint(string message, float duration = 3f) /// /// Forces the light containment zone decontamination process. /// - public static void StartDecontamination() => DecontaminationController.Singleton.ForceDecontamination(); + public static void StartDecontamination() => DecontaminationController.Singleton.FinishDecontamination(); /// /// Turns on all lights in the facility. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 7c43fc9b79..197812f710 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -610,7 +610,11 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences /// /// Gets a value indicating whether the player is jumping. /// - public bool IsJumping => Role is FpcRole fpc && fpc.FirstPersonController.FpcModule.Motor.IsJumping; + public bool IsJumping + { + get => Role is FpcRole fpc && fpc.FirstPersonController.FpcModule.Motor.JumpController.IsJumping; + set => _ = Role is FpcRole fpc ? fpc.FirstPersonController.FpcModule.Motor.JumpController.IsJumping = value : _ = value; + } /// /// Gets the player's IP address. diff --git a/EXILED/Exiled.API/Features/Toys/Capybara.cs b/EXILED/Exiled.API/Features/Toys/Capybara.cs index 23e3e07f23..e15b3600d6 100644 --- a/EXILED/Exiled.API/Features/Toys/Capybara.cs +++ b/EXILED/Exiled.API/Features/Toys/Capybara.cs @@ -38,8 +38,8 @@ internal Capybara(CapybaraToy capybaraToy) /// public bool Collidable { - get => Base.Network_collisionsEnabled; - set => Base.Network_collisionsEnabled = value; + get => Base.NetworkCollisionsEnabled; + set => Base.NetworkCollisionsEnabled = value; } } } diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 4c9d833bc4..676ba8bad3 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, 1); + public static readonly Version RequiredSCPSLVersion = new(14, 1, 0, 3); } } \ No newline at end of file diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index f4d027231f..1f5d7669fe 100644 --- a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md +++ b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md @@ -156,7 +156,6 @@ Last Update (14.1.0.1) - [HitboxType](#hitboxtype) - [HolidayType](#holidaytype) - [HotkeysTranslation](#hotkeystranslation) -- [HttpQueryMode](#httpquerymode) - [IcomText](#icomtext) - [IconType](#icontype) - [IndicatorType](#indicatortype) @@ -198,6 +197,7 @@ Last Update (14.1.0.1) - [MixerChannel](#mixerchannel) - [Mode](#mode) - [Mode](#mode) +- [Mode](#mode) - [ModifierMode](#modifiermode) - [Modules](#modules) - [MsgType](#msgtype) @@ -234,7 +234,6 @@ Last Update (14.1.0.1) - [PopupState](#popupstate) - [PortMapper](#portmapper) - [PrimitiveFlags](#primitiveflags) -- [ProcessCreationFlags](#processcreationflags) - [RaClipBoardType](#raclipboardtype) - [RadioCommand](#radiocommand) - [RadioRangeLevel](#radiorangelevel) @@ -251,6 +250,7 @@ Last Update (14.1.0.1) - [RoleChangeReason](#rolechangereason) - [RoleSpawnFlags](#rolespawnflags) - [RoleTypeId](#roletypeid) +- [RoomLevelName](#roomlevelname) - [RoomName](#roomname) - [RoomShape](#roomshape) - [RootCullablePriority](#rootcullablepriority) @@ -322,6 +322,10 @@ Last Update (14.1.0.1) - [UISetting](#uisetting) - [UnconnectedMessageType](#unconnectedmessagetype) - [UpdateMessageFlags](#updatemessageflags) +- [UpdateType](#updatetype) +- [UpdateType](#updatetype) +- [UpdateType](#updatetype) +- [UpdateType](#updatetype) - [UrgencyLevel](#urgencylevel) - [UserResponseMode](#userresponsemode) - [ValidationError](#validationerror) @@ -2440,19 +2444,6 @@ Last Update (14.1.0.1) -### HttpQueryMode - -
HttpQueryMode - -``` - [0] = HttpClient - [1] = HttpProxy - [2] = UnityWebRequest - [3] = UnityWebRequestDispatcher -``` - -
- ### IcomText
PlayerRoles.Voice.IntercomDisplay+IcomText @@ -2943,6 +2934,7 @@ Last Update (14.1.0.1) ``` [1] = FriendlyFire + [2] = DisabledAchievements ```
@@ -3145,6 +3137,17 @@ Last Update (14.1.0.1) ### Mode +
ManagedHttp+Mode + +``` + [0] = HttpClient + [1] = UnityWebRequest +``` + +
+ +### Mode +
InventorySystem.Items.Firearms.Extensions.ViewmodelReloadOnlyLayerExtension+Mode ``` @@ -3719,33 +3722,6 @@ Last Update (14.1.0.1)
-### ProcessCreationFlags - -
_Scripts.Utils.StartExternalProcess+ProcessCreationFlags - -``` - [0] = NONE - [1] = DEBUG_PROCESS - [2] = DEBUG_ONLY_THIS_PROCESS - [4] = CREATE_SUSPENDED - [8] = DETACHED_PROCESS - [16] = CREATE_NEW_CONSOLE - [512] = CREATE_NEW_PROCESS_GROUP - [1024] = CREATE_UNICODE_ENVIRONMENT - [2048] = CREATE_SEPARATE_WOW_VDM - [4096] = CREATE_SHARED_WOW_VDM - [65536] = INHERIT_PARENT_AFFINITY - [262144] = CREATE_PROTECTED_PROCESS - [524288] = EXTENDED_STARTUPINFO_PRESENT - [4194304] = CREATE_SECURE_PROCESS - [16777216] = CREATE_BREAKAWAY_FROM_JOB - [33554432] = CREATE_PRESERVE_CODE_AUTHZ_LEVEL - [67108864] = CREATE_DEFAULT_ERROR_MODE - [134217728] = CREATE_NO_WINDOW -``` - -
- ### RaClipBoardType
RemoteAdmin.Communication.RaClipboard+RaClipBoardType @@ -3999,6 +3975,18 @@ Last Update (14.1.0.1)
+### RoomLevelName + +
MapGeneration.Rooms.RoomLevelName + +``` + [0] = Main + [1] = NukeSilo + [2] = Hcz049Sublevel +``` + +
+ ### RoomName
MapGeneration.RoomName @@ -5290,6 +5278,55 @@ Last Update (14.1.0.1)
+### UpdateType + +
UserSettings.ServerSpecific.SSDropdownSetting+UpdateType + +``` + [0] = None + [1] = Selection + [2] = Options +``` + +
+ +### UpdateType + +
UserSettings.ServerSpecific.SSPlaintextSetting+UpdateType + +``` + [0] = None + [1] = Clear + [2] = Text + [3] = Settings +``` + +
+ +### UpdateType + +
UserSettings.ServerSpecific.SSSliderSetting+UpdateType + +``` + [0] = None + [1] = Value + [2] = Constraints +``` + +
+ +### UpdateType + +
UserSettings.ServerSpecific.SSTwoButtonsSetting+UpdateType + +``` + [0] = None + [1] = Value + [2] = Options +``` + +
+ ### UrgencyLevel
Christmas.Scp2536.UrgencyLevel From 9e87dcde1fa614aafbdd941cf86df5976705730e Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 2 Aug 2025 00:36:30 +0200 Subject: [PATCH 076/224] Revert "feat: Add priority for event handlers to Exiled Events (#423)" This reverts commit eb6ac982b0092ec7dce8c4306c217a2ae1a3c789. --- .gitignore | 5 +- EXILED/Exiled.Events/Features/Event.cs | 134 ++++------------------ EXILED/Exiled.Events/Features/Event{T}.cs | 134 ++++------------------ 3 files changed, 45 insertions(+), 228 deletions(-) diff --git a/.gitignore b/.gitignore index 722b747e7c..958e3428c7 100644 --- a/.gitignore +++ b/.gitignore @@ -376,7 +376,4 @@ _site/ JSON/ # Mac DS_Store -.DS_Store - -# Code Rush -/EXILED/.cr/* +.DS_Store \ No newline at end of file diff --git a/EXILED/Exiled.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index 88b22843f0..2e12e16027 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -30,20 +30,8 @@ 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; /// @@ -54,6 +42,10 @@ public Event() EventsValue.Add(this); } + private event CustomEventHandler InnerEvent; + + private event CustomAsyncEventHandler InnerAsyncEvent; + /// /// Gets a of which contains all the instances. /// @@ -112,14 +104,6 @@ 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!"); @@ -129,21 +113,7 @@ public void Subscribe(CustomEventHandler handler, int priority) patched = true; } - 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); - } + InnerEvent += handler; } /// @@ -151,14 +121,6 @@ public void Subscribe(CustomEventHandler handler, int priority) /// /// 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!"); @@ -168,21 +130,7 @@ public void Subscribe(CustomAsyncEventHandler handler, int priority) patched = true; } - 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); - } + InnerAsyncEvent += handler; } /// @@ -191,9 +139,7 @@ public void Subscribe(CustomAsyncEventHandler handler, int priority) /// The handler to add. public void Unsubscribe(CustomEventHandler handler) { - int index = innerEvent.FindIndex(p => p.handler == handler); - if (index != -1) - innerEvent.RemoveAt(index); + InnerEvent -= handler; } /// @@ -202,9 +148,7 @@ public void Unsubscribe(CustomEventHandler handler) /// The handler to add. public void Unsubscribe(CustomAsyncEventHandler handler) { - int index = innerAsyncEvent.FindIndex(p => p.handler == handler); - if (index != -1) - innerAsyncEvent.RemoveAt(index); + InnerAsyncEvent -= handler; } /// @@ -212,61 +156,25 @@ public void Unsubscribe(CustomAsyncEventHandler handler) /// public void InvokeSafely() { - BlendedInvoke(); - } - - /// - internal void BlendedInvoke() - { - Registration[] innerEvent = this.innerEvent.ToArray(); - AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); - int count = innerEvent.Length + innerAsyncEvent.Length; - int eventIndex = 0, asyncEventIndex = 0; - - 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++; - } - } + InvokeNormal(); + InvokeAsync(); } /// internal void InvokeNormal() { - Registration[] innerEvent = this.innerEvent.ToArray(); - foreach (Registration registration in innerEvent) + if (InnerEvent is null) + return; + + foreach (CustomEventHandler handler in InnerEvent.GetInvocationList().Cast()) { try { - registration.handler(); + handler(); } catch (Exception 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}"); + Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } @@ -274,16 +182,18 @@ internal void InvokeNormal() /// internal void InvokeAsync() { - AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); - foreach (AsyncRegistration registration in innerAsyncEvent) + if (InnerAsyncEvent is null) + return; + + foreach (CustomAsyncEventHandler handler in InnerAsyncEvent.GetInvocationList().Cast()) { try { - Timing.RunCoroutine(registration.handler()); + Timing.RunCoroutine(handler()); } catch (Exception 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}"); + Log.Error($"Method \"{handler.Method.Name}\" of the class \"{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 2d6252b91e..e9b6e546b9 100644 --- a/EXILED/Exiled.Events/Features/Event{T}.cs +++ b/EXILED/Exiled.Events/Features/Event{T}.cs @@ -36,20 +36,8 @@ 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; /// @@ -60,6 +48,10 @@ public Event() TypeToEvent.Add(typeof(T), this); } + private event CustomEventHandler InnerEvent; + + private event CustomAsyncEventHandler InnerAsyncEvent; + /// /// Gets a of which contains all the instances. /// @@ -118,14 +110,6 @@ 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!"); @@ -135,21 +119,7 @@ public void Subscribe(CustomEventHandler handler, int priority) patched = true; } - 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); - } + InnerEvent += handler; } /// @@ -157,14 +127,6 @@ public void Subscribe(CustomEventHandler handler, int priority) /// /// 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!"); @@ -174,21 +136,7 @@ public void Subscribe(CustomAsyncEventHandler handler, int priority) patched = true; } - 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); - } + InnerAsyncEvent += handler; } /// @@ -197,9 +145,7 @@ public void Subscribe(CustomAsyncEventHandler handler, int priority) /// The handler to add. public void Unsubscribe(CustomEventHandler handler) { - int index = innerEvent.FindIndex(p => p.handler == handler); - if (index != -1) - innerEvent.RemoveAt(index); + InnerEvent -= handler; } /// @@ -208,9 +154,7 @@ public void Unsubscribe(CustomEventHandler handler) /// The handler to add. public void Unsubscribe(CustomAsyncEventHandler handler) { - int index = innerAsyncEvent.FindIndex(p => p.handler == handler); - if (index != -1) - innerAsyncEvent.RemoveAt(index); + InnerAsyncEvent -= handler; } /// @@ -220,61 +164,25 @@ public void Unsubscribe(CustomAsyncEventHandler handler) /// Event or its arg is . public void InvokeSafely(T arg) { - BlendedInvoke(arg); - } - - /// - internal void BlendedInvoke(T arg) - { - Registration[] innerEvent = this.innerEvent.ToArray(); - AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); - int count = innerEvent.Length + innerAsyncEvent.Length; - int eventIndex = 0, asyncEventIndex = 0; - - 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++; - } - } + InvokeNormal(arg); + InvokeAsync(arg); } /// internal void InvokeNormal(T arg) { - Registration[] innerEvent = this.innerEvent.ToArray(); - foreach (Registration registration in innerEvent) + if (InnerEvent is null) + return; + + foreach (CustomEventHandler handler in InnerEvent.GetInvocationList().Cast>()) { try { - registration.handler(arg); + handler(arg); } catch (Exception 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}"); + Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } @@ -282,16 +190,18 @@ internal void InvokeNormal(T arg) /// internal void InvokeAsync(T arg) { - AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray(); - foreach (AsyncRegistration registration in innerAsyncEvent) + if (InnerAsyncEvent is null) + return; + + foreach (CustomAsyncEventHandler handler in InnerAsyncEvent.GetInvocationList().Cast>()) { try { - Timing.RunCoroutine(registration.handler(arg)); + Timing.RunCoroutine(handler(arg)); } catch (Exception 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}"); + Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}"); } } } From 597cef67d81dfac79a75528ab8c47d9d7579d88f Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:43:31 -0400 Subject: [PATCH 077/224] fix: Custom Scp127 clipsizes causing desyncs (#599) * Fix nullability and use proper methods * Made more methods use wrappers --- .../Exiled.CustomItems/API/Features/CustomWeapon.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs index 277037494e..c1c049876d 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs @@ -17,7 +17,6 @@ namespace Exiled.CustomItems.API.Features using Exiled.API.Features.Items; using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Player; - using InventorySystem; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; @@ -214,7 +213,7 @@ private void OnInternalReloading(ReloadingWeaponEventArgs ev) if (!Check(ev.Player.CurrentItem)) return; - if (ClipSize > 0 && ev.Firearm.Base.GetTotalStoredAmmo() >= ClipSize) + if (ClipSize > 0 && ev.Firearm.TotalAmmo >= ClipSize) { ev.IsAllowed = false; return; @@ -230,7 +229,7 @@ private void OnInternalReloaded(ReloadedWeaponEventArgs ev) if (ClipSize > 0) { - int ammoChambered = ((AutomaticActionModule)ev.Firearm.Base.Modules.FirstOrDefault(x => x is AutomaticActionModule))?.SyncAmmoChambered ?? 0; + int ammoChambered = ((AutomaticActionModule?)ev.Firearm.Base.Modules.FirstOrDefault(x => x is AutomaticActionModule))?.SyncAmmoChambered ?? 0; int ammoToGive = ClipSize - ammoChambered; AmmoType ammoType = ev.Firearm.AmmoType; @@ -241,13 +240,13 @@ private void OnInternalReloaded(ReloadedWeaponEventArgs ev) if (ammoToGive < ammoInInventory) { ev.Firearm.MagazineAmmo = ammoToGive; - int newAmmo = ev.Player.Inventory.GetCurAmmo(ammoType.GetItemType()) + ammoDrop; - ev.Player.Inventory.ServerSetAmmo(ammoType.GetItemType(), newAmmo); + int newAmmo = ev.Player.GetAmmo(ammoType) + ammoDrop; + ev.Player.SetAmmo(ammoType, (ushort)newAmmo); } else { ev.Firearm.MagazineAmmo = ammoInInventory; - ev.Player.Inventory.ServerSetAmmo(ammoType.GetItemType(), 0); + ev.Player.SetAmmo(ammoType, 0); } } From 30b607bbe67b8b871cf8da2a43ca684a49569b39 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:44:50 -0400 Subject: [PATCH 078/224] fix: Fix 3114 Dancing + minor docs fix (#601) Fix 3114 Dancing + minor docs fix --- EXILED/Exiled.API/Features/Player.cs | 2 +- .../Patches/Events/Scp3114/Dancing.cs | 48 +++++-------------- 2 files changed, 12 insertions(+), 38 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 197812f710..884230151f 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -608,7 +608,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public bool HasFlashlightModuleEnabled => CurrentItem is Firearm firearm && firearm.FlashlightEnabled; /// - /// Gets a value indicating whether the player is jumping. + /// Gets or sets a value indicating whether the player is jumping. /// public bool IsJumping { diff --git a/EXILED/Exiled.Events/Patches/Events/Scp3114/Dancing.cs b/EXILED/Exiled.Events/Patches/Events/Scp3114/Dancing.cs index 60e5863c99..53798e1a31 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp3114/Dancing.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp3114/Dancing.cs @@ -7,8 +7,6 @@ namespace Exiled.Events.Patches.Events.Scp3114 { -#pragma warning disable SA1402 // File may only contain a single type - using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; @@ -35,7 +33,7 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Brfalse_S); + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Brfalse); Label continueLabel = generator.DefineLabel(); @@ -58,7 +56,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Ldc_I4_0); - ListPool.Pool.Return(newInstructions); - } - } + newInstructions.RemoveRange(index, 3); - /// - /// Patches - /// to add event. - /// - [HarmonyPatch(typeof(Scp3114Dance), nameof(Scp3114Dance.ServerWriteRpc))] - internal class ChooseDanceType - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - int offset = -4; - int index = newInstructions.FindIndex(x => x.operand == (object)Method(typeof(NetworkWriter), nameof(NetworkWriter.WriteByte))) + offset; - - newInstructions.RemoveRange(index, 4); - - // replace "writer.WriteByte((byte)UnityEngine.Random.Range(0, 255))" - // with "writer.WriteByte(ChooseDanceType.DanceType)" - newInstructions.InsertRange(index, new CodeInstruction[] + newInstructions.InsertRange( + index, + new[] { - // Handle(Player.Get(this.Owner)); - new(OpCodes.Ldarg_0), - - new(OpCodes.Call, Method(typeof(ChooseDanceType), nameof(Handle))), + new CodeInstruction(OpCodes.Ldloc, ev), + new(OpCodes.Call, PropertyGetter(typeof(DancingEventArgs), nameof(DancingEventArgs.DanceType))), }); for (int z = 0; z < newInstructions.Count; z++) @@ -114,8 +91,5 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - - private static byte Handle(Scp3114Dance scp3114Dance) - => (byte)Player.Get(scp3114Dance.Owner).Role.As().DanceType; } } \ No newline at end of file From cc0082ce89e2b4f244ddcb2c93207fd5c3371404 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 2 Aug 2025 00:45:27 +0200 Subject: [PATCH 079/224] Fix error --- EXILED/Exiled.Events/Features/Event.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EXILED/Exiled.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index 2e12e16027..9885747c7e 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -9,6 +9,7 @@ namespace Exiled.Events.Features { using System; using System.Collections.Generic; + using System.Linq; using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; From bcdf92be0ac8b5d349aa167ef4e52eced83e4372 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 2 Aug 2025 01:08:46 +0200 Subject: [PATCH 080/224] Reapply "feat: Add priority for event handlers to Exiled Events (#423)" This reverts commit 9e87dcde1fa614aafbdd941cf86df5976705730e. --- .gitignore | 5 +- EXILED/Exiled.Events/Features/Event.cs | 134 ++++++++++++++++++---- EXILED/Exiled.Events/Features/Event{T}.cs | 134 ++++++++++++++++++---- 3 files changed, 228 insertions(+), 45 deletions(-) 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.Events/Features/Event.cs b/EXILED/Exiled.Events/Features/Event.cs index 9885747c7e..bbbc638401 100644 --- a/EXILED/Exiled.Events/Features/Event.cs +++ b/EXILED/Exiled.Events/Features/Event.cs @@ -31,8 +31,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 +55,6 @@ public Event() EventsValue.Add(this); } - private event CustomEventHandler InnerEvent; - - private event CustomAsyncEventHandler InnerAsyncEvent; - /// /// Gets a of which contains all the instances. /// @@ -105,6 +113,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 +130,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 +152,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 +169,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 +192,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 +203,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 +213,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 +275,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}"); } } } From 9ee469cf399e5f92521d37a4cca103921c02d394 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 2 Aug 2025 05:05:34 -0400 Subject: [PATCH 081/224] fix: fix transpilers for 14.1.3 (#602) * Fix 3114 Dancing + minor docs fix * Fix PrefabManager, ChangingAttachments Transpiler, Escaping and Escaped transpiler, and Shooting transpiler --- .../Handlers/Internal/ClientStarted.cs | 20 +++++++++++++++++-- .../Events/Item/ChangingAttachments.cs | 4 ++-- .../Events/Player/EscapingAndEscaped.cs | 18 ++++++++--------- .../Patches/Events/Player/Shooting.cs | 4 ---- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs index 46e1f5881d..7fd6df8af0 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs @@ -33,14 +33,30 @@ public static void OnClientStarted() foreach (KeyValuePair prefab in NetworkClient.prefabs) { - if(!prefabs.ContainsKey(prefab.Key) && prefab.Value.TryGetComponent(out Component component)) + if (!prefabs.ContainsKey(prefab.Key)) + { + if (!prefab.Value.TryGetComponent(out NetworkBehaviour component)) + { + Log.Error($"Failed to get component for prefab: {prefab.Value.name} ({prefab.Key})"); + continue; + } + prefabs.Add(prefab.Key, (prefab.Value, component)); + } } foreach (NetworkIdentity ragdollPrefab in RagdollManager.AllRagdollPrefabs) { - if(!prefabs.ContainsKey(ragdollPrefab.assetId) && ragdollPrefab.gameObject.TryGetComponent(out Component component)) + if (!prefabs.ContainsKey(ragdollPrefab.assetId)) + { + if (!ragdollPrefab.TryGetComponent(out BasicRagdoll component)) + { + Log.Error($"Failed to get component for ragdoll prefab: {ragdollPrefab.name}"); + continue; + } + prefabs.Add(ragdollPrefab.assetId, (ragdollPrefab.gameObject, component)); + } } for (int i = 0; i < EnumUtils.Values.Length; i++) diff --git a/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs b/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs index e4069148ff..6e45fbd152 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/ChangingAttachments.cs @@ -50,7 +50,7 @@ private static IEnumerable Transpiler(IEnumerable i.IsLdarg(1)) - 1; + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Stloc_2) - 2; List Scp1576, + + /// + /// . + /// + Lightweight, + + /// + /// . + /// + HeavyFooted, + + /// + /// . + /// + Fade, } } diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index d70ee9839b..c6b2df1164 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -77,6 +77,9 @@ public static class EffectTypeExtension { EffectType.Blurred, typeof(Blurred) }, { EffectType.Scp1344Detected, typeof(Scp1344Detected) }, { EffectType.Scp1576, typeof(Scp1576) }, + { EffectType.Lightweight, typeof(Lightweight) }, + { EffectType.HeavyFooted, typeof(HeavyFooted) }, + { EffectType.Fade, typeof(Fade) }, #pragma warning disable CS0618 { EffectType.Marshmallow, typeof(MarshmallowEffect) }, { EffectType.BecomingFlamingo, typeof(BecomingFlamingo) }, From 6fedfa8d1ca3a05366d45e934e752e2c715042c2 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 2 Aug 2025 12:29:35 +0200 Subject: [PATCH 084/224] this should work but it's do not idk why --- .../Patches/Events/Player/ChangedRoom.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs index 70b39fb467..5a74d90b86 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs @@ -34,7 +34,7 @@ private static IEnumerable Transpiler(IEnumerable x == (object)Method(typeof(PlayerEvents), "op_Inequality", new[] { typeof(Object), typeof(Object) })) + offset; + int index = newInstructions.FindIndex(x => x == (object)Method(typeof(Object), "op_Inequality", new[] { typeof(Object), typeof(Object) })) + offset; newInstructions[index].labels.Add(jump); @@ -44,22 +44,8 @@ private static IEnumerable Transpiler(IEnumerable(); 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_2), - - // newRoom - new(OpCodes.Ldloc_3), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._lastDetected))), // RoomChangedEventArgs ev = new RoomChangedEventArgs(hub, oldRoom, newRoom); new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RoomChangedEventArgs))[0]), From 7307836f095d8c5af38607208c913e36b5f2b64f Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 2 Aug 2025 06:43:11 -0400 Subject: [PATCH 085/224] fix: Firearm pickups spawning without ammo (#600) Fix firearm pickups spawning without ammo --- EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs index b1534a0ef8..d4ee5e8126 100644 --- a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs @@ -157,6 +157,7 @@ protected override void InitializeProperties(ItemBase itemBase) return; } + Ammo = magazine.AmmoMax; MaxAmmo = magazine.AmmoMax; } } From 7a205db236bc5eaf5d4cc0b3db74c4b294c8c9fd Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:23:47 -0400 Subject: [PATCH 086/224] feat: more SSS for 14.1.3 (#604) Obsolete old ctors, add new Update methods --- .../Core/UserSettings/ButtonSetting.cs | 13 ++++ .../Core/UserSettings/DropdownSetting.cs | 56 ++++++++++++++++- .../Core/UserSettings/HeaderSetting.cs | 21 ++++++- .../Core/UserSettings/KeybindSetting.cs | 23 ++++++- .../Features/Core/UserSettings/SettingBase.cs | 40 +++++++++++++ .../Core/UserSettings/SliderSetting.cs | 53 +++++++++++++++- .../Core/UserSettings/TwoButtonsSetting.cs | 47 ++++++++++++++- .../Core/UserSettings/UserTextInputSetting.cs | 60 ++++++++++++++++++- 8 files changed, 305 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs index b150e01a44..46938d13b4 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs @@ -76,6 +76,19 @@ public float HoldTime set => Base.HoldTimeSeconds = value; } + /// + /// Sends updated values to clients. + /// + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateSetting(string text, float holdTime, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendButtonUpdate(text, holdTime, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Returns a representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs index 88886839bd..ada19ec63a 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs @@ -30,6 +30,34 @@ public class DropdownSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Will be removed in Exiled 10 in favour of ctor with more params")] + public DropdownSetting( + int id, + string label, + IEnumerable options, + int defaultOptionIndex, + SSDropdownSetting.DropdownEntryType dropdownEntryType, + string hintDescription, + HeaderSetting header, + Action onChanged) + : base(new SSDropdownSetting(id, label, options.ToArray(), defaultOptionIndex, dropdownEntryType, hintDescription), header, onChanged) + { + Base = (SSDropdownSetting)base.Base; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// public DropdownSetting( int id, string label, @@ -37,9 +65,11 @@ public DropdownSetting( int defaultOptionIndex = 0, SSDropdownSetting.DropdownEntryType dropdownEntryType = SSDropdownSetting.DropdownEntryType.Regular, string hintDescription = null, + byte collectionId = byte.MaxValue, + bool isServerOnly = false, HeaderSetting header = null, Action onChanged = null) - : base(new SSDropdownSetting(id, label, options.ToArray(), defaultOptionIndex, dropdownEntryType, hintDescription), header, onChanged) + : base(new SSDropdownSetting(id, label, options.ToArray(), defaultOptionIndex, dropdownEntryType, hintDescription, collectionId, isServerOnly), header, onChanged) { Base = (SSDropdownSetting)base.Base; } @@ -116,6 +146,30 @@ public string SelectedOption set => SelectedIndex = Array.IndexOf(Base.Options, value); } + /// + /// Sends updated values to clients. + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateSetting(string[] options, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendDropdownUpdate(options, overrideValue, hub => filter(Player.Get(hub))); + } + + /// + /// If setting is server only, sends updated values to clients. + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateValue(int selectedIndex, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendValueUpdate(selectedIndex, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Gets a string representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/HeaderSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/HeaderSetting.cs index 78fd4ee32d..4b13238b65 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/HeaderSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/HeaderSetting.cs @@ -7,6 +7,8 @@ namespace Exiled.API.Features.Core.UserSettings { + using System; + using Exiled.API.Interfaces; using global::UserSettings.ServerSpecific; @@ -21,7 +23,8 @@ public class HeaderSetting : SettingBase, IWrapper /// /// /// - public HeaderSetting(string name, string hintDescription = "", bool paddling = false) + [Obsolete("Use constructor with Id, old headers will use random number based on headers name")] + public HeaderSetting(string name, string hintDescription, bool paddling) : this(new SSGroupHeader(name, paddling, hintDescription)) { Base = (SSGroupHeader)base.Base; @@ -29,6 +32,21 @@ public HeaderSetting(string name, string hintDescription = "", bool paddling = f Base.SetId(null, name); } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public HeaderSetting(int id, string name, string hintDescription = "", bool padding = false) + : this(new SSGroupHeader(id, name, padding, hintDescription)) + { + Base = (SSGroupHeader)base.Base; + + Base.SetId(id, name); + } + /// /// Initializes a new instance of the class. /// @@ -46,6 +64,7 @@ internal HeaderSetting(SSGroupHeader settingBase) /// /// Gets or sets a value indicating whether to reduce padding. /// + // TODO: change to ReducedPadding (thanks Valera) public bool ReducedPaddling { get => Base.ReducedPadding; diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index 9fc693b7ee..e3b40e8f0c 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -28,7 +28,7 @@ public class KeybindSetting : SettingBase, IWrapper /// /// /// - [Obsolete("This method will be removed next major version because of a new feature. Use the constructor with \"allowSpectator\" instead.")] + [Obsolete("This method will be removed next major version because of a new feature. Use the constructor with \"CollectionId\" 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) { @@ -46,12 +46,31 @@ public KeybindSetting(int id, string label, KeyCode suggested, bool preventInter /// /// /// - public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, bool allowSpectatorTrigger = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) + [Obsolete("Will be removed in Exiled 10 in favour of ctor with more params.")] + public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI, bool allowSpectatorTrigger, string hintDescription, HeaderSetting header, Action onChanged) : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, allowSpectatorTrigger, 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 = "", byte collectionId = byte.MaxValue, HeaderSetting header = null, Action onChanged = null) + : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, allowSpectatorTrigger, hintDescription, collectionId), header, onChanged) + { + Base = (SSKeybindSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 2cbc85e645..b95b57b344 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -108,6 +108,33 @@ public string HintDescription set => Base.HintDescription = value; } + /// + /// Gets or sets a value indicating whether the setting receives updates from server (and client stops sending updates). + /// + /// + /// Useful for displaying information, you cannot receive updates from a setting with this enabled. + /// + public bool IsServerOnly + { + get => Base.IsServerOnly; + set => Base.IsServerOnly = value; + } + + /// + /// Gets or sets a value controlling if this setting is shared across servers with same Ip address!. Default value is 255. + /// + /// + /// Settings with this value between 0 and 20 will store the servers Ip (or other unique identifier) instead of port, meaning the client treats a setting between 1.1.1.1:7777 and 1.1.1.1:7778 the same. + ///
+ ///
+ /// If this value is above 20, the aforementioned behavior will not occur and the setting will behave as normal. + ///
+ public byte CollectionId + { + get => Base.CollectionId; + set => Base.CollectionId = value; + } + /// /// Gets the response mode of this setting. /// @@ -334,6 +361,19 @@ public static IEnumerable Unregister(Player player, IEnumerable + /// Sends an updated label and hint to clients. + ///
+ /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateLabelAndHint(string label, string hint, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendUpdate(label, hint, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Returns a string representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SliderSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SliderSetting.cs index 66d88174d9..abb522fd76 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SliderSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SliderSetting.cs @@ -29,12 +29,35 @@ public class SliderSetting : SettingBase, IWrapper /// /// /// - public SliderSetting(int id, string label, float minValue, float maxValue, float defaultValue, bool isInteger = false, string stringFormat = "0.##", string displayFormat = "{0}", string hintDescription = null) + [Obsolete("Will be removed in Exiled 10 in favour of ctor with more params.")] + public SliderSetting(int id, string label, float minValue, float maxValue, float defaultValue, bool isInteger, string stringFormat, string displayFormat, string hintDescription) : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, isInteger, stringFormat, displayFormat, hintDescription)) { Base = (SSSliderSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public SliderSetting(int id, string label, float minValue, float maxValue, float defaultValue, bool isInteger = false, string stringFormat = "0.##", string displayFormat = "{0}", string hintDescription = null, byte collectionId = byte.MaxValue, bool isServerOnly = false, HeaderSetting header = null, Action onChanged = null) + : base(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, isInteger, stringFormat, displayFormat, hintDescription, collectionId, isServerOnly), header, onChanged) + { + Base = (SSSliderSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// @@ -112,6 +135,34 @@ public string DisplayFormat /// public new SSSliderSetting Base { get; } + /// + /// Sends updated values to clients. + /// + /// + /// + /// + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateSetting(float min, float max, bool isInteger, string stringFormat, string displayFormat, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendSliderUpdate(min, max, isInteger, stringFormat, displayFormat, overrideValue, hub => filter(Player.Get(hub))); + } + + /// + /// If setting is server only, sends updated values to clients. + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateValue(float value, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendValueUpdate(value, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Returns a representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs index 52c596ae76..18233e6ffc 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs @@ -28,12 +28,32 @@ public class TwoButtonsSetting : SettingBase, IWrapper /// /// /// - public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) + [Obsolete("Will be removed in Exiled 10 in favour of ctor with more params.")] + public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond, string hintDescription, HeaderSetting header, Action onChanged) : base(new SSTwoButtonsSetting(id, label, firstOption, secondOption, defaultIsSecond, hintDescription), header, onChanged) { Base = (SSTwoButtonsSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond = false, string hintDescription = "", byte collectionId = byte.MaxValue, bool isServerOnly = false, HeaderSetting header = null, Action onChanged = null) + : base(new SSTwoButtonsSetting(id, label, firstOption, secondOption, defaultIsSecond, hintDescription, collectionId, isServerOnly), header, onChanged) + { + Base = (SSTwoButtonsSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// @@ -98,6 +118,31 @@ public string SecondOption set => Base.OptionB = value; } + /// + /// Sends updated values to clients. + /// + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateSetting(string firstOption, string secondOption, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendTwoButtonUpdate(firstOption, secondOption, overrideValue, hub => filter(Player.Get(hub))); + } + + /// + /// If setting is server only, sends updated values to clients. + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateValue(bool isSecond, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendValueUpdate(isSecond, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Returns a representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/UserTextInputSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/UserTextInputSetting.cs index 8944558e03..99c163581e 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/UserTextInputSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/UserTextInputSetting.cs @@ -26,13 +26,33 @@ public class UserTextInputSetting : SettingBase, IWrapper /// /// /// - /// /// - public UserTextInputSetting(int id, string label, string placeHolder = "", int characterLimit = 64, TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard, string hintDescription = null) + /// + [Obsolete("Will be removed in Exiled 10 in favour of ctor with more params.")] + public UserTextInputSetting(int id, string label, string placeHolder, int characterLimit, TMP_InputField.ContentType contentType, string hintDescription) : this(new SSPlaintextSetting(id, label, placeHolder, characterLimit, contentType, hintDescription)) { Base = (SSPlaintextSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UserTextInputSetting(int id, string label, string placeHolder = "", int characterLimit = 64, TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard, string hintDescription = null, byte collectionId = byte.MaxValue, bool isServerOnly = false, HeaderSetting header = null, Action onChanged = null) + : base(new SSPlaintextSetting(id, label, placeHolder, characterLimit, contentType, hintDescription, collectionId, isServerOnly), header, onChanged) + { + Base = (SSPlaintextSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// @@ -78,6 +98,42 @@ public int CharacterLimit set => Base.CharacterLimit = value; } + /// + /// Requests clients TextInputs to be cleared. + /// + /// Who to send the request to. + public void RequestClear(Predicate filter = null) + { + filter ??= _ => true; + Base.SendClearRequest(hub => filter(Player.Get(hub))); + } + + /// + /// Sends updated values to clients. + /// + /// + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateSetting(string placeholder, ushort characterLimit, TMP_InputField.ContentType contentType, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendPlaintextUpdate(placeholder, characterLimit, contentType, overrideValue, hub => filter(Player.Get(hub))); + } + + /// + /// If setting is server only, sends updated values to clients. + /// + /// + /// If false, sends fake values. + /// Who to send the update to. + public void UpdateValue(string value, bool overrideValue = true, Predicate filter = null) + { + filter ??= _ => true; + Base.SendValueUpdate(value, overrideValue, hub => filter(Player.Get(hub))); + } + /// /// Returns a representation of this . /// From 3eb271753376665d6be63d6567ce9328a80d9f0a Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:23:57 -0400 Subject: [PATCH 087/224] fix: ReceivingPreference, AnnouncingScpTermination, ChangedRoom, and Jumping transpilers and patches (#603) * Fix transpilers * doc * alter jumping and fix ChangingRadioPreset --------- Co-authored-by: Yamato --- .../EventArgs/Player/JumpingEventArgs.cs | 2 +- .../Events/Item/ReceivingPreference.cs | 28 ++++-------- .../Events/Map/AnnouncingScpTermination.cs | 2 +- .../Patches/Events/Player/ChangedRoom.cs | 9 +++- .../Events/Player/ChangingRadioPreset.cs | 4 +- .../Patches/Events/Player/Jumping.cs | 43 +++++++++++++++---- 6 files changed, 55 insertions(+), 33 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/JumpingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/JumpingEventArgs.cs index 0a1c34b634..410897b531 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/JumpingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/JumpingEventArgs.cs @@ -57,7 +57,7 @@ public float Speed } /// - /// Gets or sets a value indicating whether the client data can be synchronized with the server. + /// Gets or sets a value indicating whether or not the player can jump. /// public bool IsAllowed { get; set; } } diff --git a/EXILED/Exiled.Events/Patches/Events/Item/ReceivingPreference.cs b/EXILED/Exiled.Events/Patches/Events/Item/ReceivingPreference.cs index 96b9907b3a..324c8cb6d4 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/ReceivingPreference.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/ReceivingPreference.cs @@ -40,10 +40,9 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); int offset = 1; - int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + offset; + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stloc_2) + offset; LocalBuilder ev = generator.DeclareLocal(typeof(ReceivingPreferenceEventArgs)); - LocalBuilder curCode = generator.DeclareLocal(typeof(uint)); Label cdc = generator.DefineLabel(); Label ret = generator.DefineLabel(); @@ -54,14 +53,6 @@ private static IEnumerable Transpiler(IEnumerable), nameof(Dictionary.TryGetValue))), - new(OpCodes.Brfalse_S, cdc), - // API::Features::Player::Get(referenceHub) new(OpCodes.Ldloc_0), new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), @@ -70,12 +61,11 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Newobj && (ConstructorInfo)i.operand == GetDeclaredConstructors(typeof(LabApi.Events.Arguments.ServerEvents.CassieQueuingScpTerminationEventArgs))[0]) + offset; newInstructions.InsertRange( diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs index 5a74d90b86..376587b14e 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs @@ -15,8 +15,8 @@ namespace Exiled.Events.Patches.Events.Player using Exiled.Events.EventArgs.Player; using Exiled.Events.Handlers; using HarmonyLib; - using LabApi.Events.Handlers; using MapGeneration; + using PlayerRoles; using UnityEngine; using static HarmonyLib.AccessTools; @@ -34,12 +34,17 @@ private static IEnumerable Transpiler(IEnumerable x == (object)Method(typeof(Object), "op_Inequality", new[] { typeof(Object), typeof(Object) })) + offset; + int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(Object), "op_Inequality", new[] { typeof(Object), typeof(Object) }))) + offset; newInstructions[index].labels.Add(jump); newInstructions.InsertRange(index, new CodeInstruction[] { + // referenceHub + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._roleManager))), + new(OpCodes.Callvirt, PropertyGetter(typeof(PlayerRoleManager), nameof(PlayerRoleManager.Hub))), + // oldRoom new(OpCodes.Ldloc_2), diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRadioPreset.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRadioPreset.cs index 4c4b27c4a3..ad77fa94ba 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRadioPreset.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRadioPreset.cs @@ -21,6 +21,8 @@ namespace Exiled.Events.Patches.Events.Player using InventorySystem.Items; using InventorySystem.Items.Radio; + using LabApi.Events.Arguments.PlayerEvents; + using static HarmonyLib.AccessTools; /// @@ -40,7 +42,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Newobj) + offset; + int index = newInstructions.FindIndex(instruction => instruction.operand == (object)Constructor(typeof(PlayerChangingRadioRangeEventArgs), new[] { typeof(ReferenceHub), typeof(RadioItem), typeof(RadioMessages.RadioRangeLevel) })) + offset; newInstructions.InsertRange( index, diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs b/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs index c7840c950a..435bcb7eca 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs @@ -34,18 +34,41 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); LocalBuilder ev = generator.DeclareLocal(typeof(JumpingEventArgs)); + LocalBuilder jumping = generator.DeclareLocal(typeof(bool)); - Label ret = generator.DefineLabel(); + Label cont = generator.DefineLabel(); + Label cancel = generator.DefineLabel(); - const int offset = 1; - int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stfld) + offset; + // fun fact, the "int num = ProcessJump() ? 1 : 0;" in target method doesn't actually store anything, the bool result from ProcessJump is simply kept on stack. + // our patch needs to know when player is jumping so we modify method to store that. + int offset = 1; + int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(FpcJumpController), nameof(FpcJumpController.ProcessJump)))) + offset; + + // after ProcessJump, store its result + newInstructions.Insert(index, new CodeInstruction(OpCodes.Stloc, jumping)); + + // offset here is 0 + index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Brfalse_S); + + // make br_false use stored value + newInstructions.Insert(index, new CodeInstruction(OpCodes.Ldloc, jumping)); + + // The FindIndex finds when storing the field inside the moveDirection vector, so our patch occurs right before "this.MoveDirection = moveDirection;" + offset = 1; + index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stfld) + offset; + + newInstructions[index].WithLabels(cont); newInstructions.InsertRange( index, new[] { + // if not jumping, skip Jumping event + new(OpCodes.Ldloc, jumping), + new(OpCodes.Brfalse, cont), + // Player.Get(this.Hub) - new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Ldarg_0), new(OpCodes.Ldfld, Field(typeof(FpcMotor), nameof(FpcMotor.Hub))), new(OpCodes.Call, Method(typeof(API.Features.Player), nameof(API.Features.Player.Get), new[] { typeof(ReferenceHub) })), @@ -59,7 +82,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Date: Sat, 2 Aug 2025 19:36:45 +0200 Subject: [PATCH 088/224] 9.7.0-rc.2 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 760aff52d3..e5cb36ae98 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.7.0-rc.1 + 9.7.0-rc.2 false From f16d22c071c9ab85414fa26bc837ff21b7708534 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 2 Aug 2025 20:14:21 +0200 Subject: [PATCH 089/224] Map.EscapeZones --- EXILED/Exiled.API/Features/Map.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 266d04c4d1..90ca019f81 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -99,6 +99,11 @@ public static bool IsDecontaminationEnabled : DecontaminationController.DecontaminationStatus.Disabled; } + /// + /// Gets the that will trigger Escape for player. + /// + public static List EscapeZones => Escape.EscapeZones; + /// /// Gets the . /// From 81f9c677e4cfe8aa187be21a5c5357d9b5a02340 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sun, 3 Aug 2025 21:42:11 +0200 Subject: [PATCH 090/224] 9.7.0 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index e5cb36ae98..6ae9fe37ff 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.7.0-rc.2 + 9.7.0 false From eb92b298544f47a62ad5be89d96ae543524e8d3f Mon Sep 17 00:00:00 2001 From: Yamato Date: Mon, 4 Aug 2025 06:36:47 +0200 Subject: [PATCH 091/224] Fix MaxAmmo being wrongly set when Dropping --- .../Exiled.API/Features/Pickups/FirearmPickup.cs | 14 +++----------- EXILED/Exiled.API/Features/Pickups/Pickup.cs | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs index d4ee5e8126..bae472a360 100644 --- a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs @@ -7,17 +7,14 @@ namespace Exiled.API.Features.Pickups { - using System; - + using Exiled.API.Features.Items; using Exiled.API.Interfaces; - using InventorySystem.Items; using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Modules; - + using System; using UnityEngine; - using BaseFirearm = InventorySystem.Items.Firearms.FirearmPickup; /// @@ -43,11 +40,7 @@ internal FirearmPickup(ItemType type) : base(type) { Base = (BaseFirearm)((Pickup)this).Base; - - // TODO not finish - /* - if (type is ItemType.ParticleDisruptor && Status.Ammo == 0) - Status = new FirearmStatus(5, FirearmStatusFlags.MagazineInserted, 0);*/ + Ammo = MaxAmmo; } /// @@ -157,7 +150,6 @@ protected override void InitializeProperties(ItemBase itemBase) return; } - Ammo = magazine.AmmoMax; MaxAmmo = magazine.AmmoMax; } } diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index fa9aca3b28..5297acae5d 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -495,7 +495,7 @@ public static IEnumerable Get(IEnumerable gameObjects) ThrownProjectile thrownProjectile => thrownProjectile switch { BaseScp018Projectile => new Projectiles.Scp018Projectile(), - ExplosionGrenade explosionGrenade => new ExplosionGrenadeProjectile(type), + ExplosionGrenade => new ExplosionGrenadeProjectile(type), FlashbangGrenade => new FlashbangProjectile(), BaseScp2176Projectile => new Projectiles.Scp2176Projectile(), EffectGrenade => new EffectGrenadeProjectile(type), From dc62365d4edc8af30a92a2a6e6ec4792bf766c4f Mon Sep 17 00:00:00 2001 From: Yamato Date: Mon, 4 Aug 2025 06:38:57 +0200 Subject: [PATCH 092/224] 9.7.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 6ae9fe37ff..762db082d6 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.7.0 + 9.7.1 false From c2afe26e254d5a2dc2fdc161e754733b7d5ca6a3 Mon Sep 17 00:00:00 2001 From: Yamato Date: Mon, 4 Aug 2025 06:51:42 +0200 Subject: [PATCH 093/224] Fix using --- EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs index bae472a360..4d7ec52765 100644 --- a/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/FirearmPickup.cs @@ -7,14 +7,14 @@ namespace Exiled.API.Features.Pickups { - using Exiled.API.Features.Items; + using System; + using Exiled.API.Interfaces; using InventorySystem.Items; using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Modules; - using System; - using UnityEngine; + using BaseFirearm = InventorySystem.Items.Firearms.FirearmPickup; /// From 67f1c449d08dde7ba10c8089d9159304b4511bb3 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:48:16 +0300 Subject: [PATCH 094/224] feat: Add FpcRole Jump method (#609) * Update FpcRole.cs * Update FpcRole.cs --- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 57ba6bcf85..42c08b449d 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -312,5 +312,15 @@ public void ResetStamina(bool multipliers = false) StaminaUsageMultiplier = 1f; StaminaRegenMultiplier = 1f; } + + /// + /// Makes the player jump using the default or a specified strength. + /// + /// Optional. The strength of the jump. If not provided, the default jump speed for Role is used. + public void Jump(float? jumpStrength = null) + { + float strength = jumpStrength ?? FirstPersonController.FpcModule.JumpSpeed; + FirstPersonController.FpcModule.Motor.JumpController.ForceJump(strength); + } } } From 29d093dadcbe49d93ca08a64712444ec168ced31 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:30:36 +0300 Subject: [PATCH 095/224] fix: ChangingDisruptorMode transpiler location (#608) * Update ChangingDisruptorModeEventArgs.cs * Update ChangingDisruptorMode.cs * Update ChangingDisruptorMode.cs * Update ChangingDisruptorMode.cs * Update ChangingDisruptorMode.cs --- .../Player/ChangingDisruptorModeEventArgs.cs | 7 ++-- .../Events/Player/ChangingDisruptorMode.cs | 41 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingDisruptorModeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingDisruptorModeEventArgs.cs index 68fe52816d..699848459b 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingDisruptorModeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingDisruptorModeEventArgs.cs @@ -11,6 +11,7 @@ namespace Exiled.Events.EventArgs.Player using Exiled.API.Features; using Exiled.API.Features.Items; using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items; /// /// Contains all information before disruptor's mode is changed. @@ -22,9 +23,9 @@ public class ChangingDisruptorModeEventArgs : IFirearmEvent /// /// /// - public ChangingDisruptorModeEventArgs(Item firearm, bool mode) + public ChangingDisruptorModeEventArgs(ItemBase firearm, bool mode) { - Firearm = firearm.As(); + Firearm = Item.Get(firearm).As(); NewMode = mode ? DisruptorMode.Disintegrator : DisruptorMode.BurstFire; } @@ -42,4 +43,4 @@ public ChangingDisruptorModeEventArgs(Item firearm, bool mode) /// public Player Player => Item.Owner; } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangingDisruptorMode.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangingDisruptorMode.cs index 37aa6376bb..1263f56daa 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangingDisruptorMode.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangingDisruptorMode.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -8,15 +8,13 @@ namespace Exiled.Events.Patches.Events.Player { using System.Collections.Generic; - using System.Linq; using System.Reflection.Emit; - using Exiled.API.Features.Items; using Exiled.API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Player; using HarmonyLib; - using InventorySystem.Items; + using InventorySystem.Items.Firearms.Modules; using Mirror; @@ -30,36 +28,37 @@ namespace Exiled.Events.Patches.Events.Player [HarmonyPatch(typeof(DisruptorModeSelector), nameof(DisruptorModeSelector.ServerProcessCmd))] internal static class ChangingDisruptorMode { - private static IEnumerable Transpiler(IEnumerable instructions) + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); - int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0); + LocalBuilder newMode = generator.DeclareLocal(typeof(bool)); + + int offset = 2; + int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(NetworkReaderExtensions), nameof(NetworkReaderExtensions.ReadBool)))); - newInstructions.InsertRange(index, new CodeInstruction[] + newInstructions.InsertRange(index + offset, new CodeInstruction[] { - // Item.Get(this.Firearm); + // this.Firearm; new(OpCodes.Ldarg_0), new(OpCodes.Callvirt, PropertyGetter(typeof(DisruptorModeSelector), nameof(DisruptorModeSelector.Firearm))), - new(OpCodes.Call, GetDeclaredMethods(typeof(Item)).Find(x => !x.IsGenericMethod && x.GetParameters().FirstOrDefault()?.ParameterType == typeof(ItemBase))), - // reader.ReadBool(); - new(OpCodes.Ldarg_1), - new(OpCodes.Call, Method(typeof(NetworkReaderExtensions), nameof(NetworkReaderExtensions.ReadBool))), + // newMode; + new(OpCodes.Ldloc, newMode), - // ChangerDisruptorModeEventArgs ev = new(Item.Get(this.Firearm), reader.ReadBool(), true); + // ChangerDisruptorModeEventArgs ev = new(this.Firearm, newMode); new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ChangingDisruptorModeEventArgs))[0]), // Handlers.Player.OnChangingDisruptorMode(ev); new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnChangingDisruptorMode))), + }); - // reader.Position--; - new(OpCodes.Ldarg_1), - new(OpCodes.Ldarg_1), - new(OpCodes.Ldfld, Field(typeof(NetworkReader), nameof(NetworkReader.Position))), - new(OpCodes.Ldc_I4_M1), - new(OpCodes.Add), - new(OpCodes.Stfld, Field(typeof(NetworkReader), nameof(NetworkReader.Position))), + offset = 1; + newInstructions.InsertRange(index + offset, new CodeInstruction[] + { + // bool newMode = reader.ReadBool(); + new(OpCodes.Dup), + new(OpCodes.Stloc, newMode), }); for (int z = 0; z < newInstructions.Count; z++) @@ -68,4 +67,4 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} \ No newline at end of file +} From cd3385815c2296fea91cd1a0d4040d3066bb92a2 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:31:37 +0300 Subject: [PATCH 096/224] fix: Delete empty files (#605) * Delete EXILED/Exiled.Events/EventArgs/Scp079/StartingSpeakerEventArgs.cs * Delete EXILED/Exiled.Events/EventArgs/Scp079/StoppingSpeakerEventArgs.cs --- EXILED/Exiled.Events/EventArgs/Scp079/StartingSpeakerEventArgs.cs | 0 EXILED/Exiled.Events/EventArgs/Scp079/StoppingSpeakerEventArgs.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 EXILED/Exiled.Events/EventArgs/Scp079/StartingSpeakerEventArgs.cs delete mode 100644 EXILED/Exiled.Events/EventArgs/Scp079/StoppingSpeakerEventArgs.cs diff --git a/EXILED/Exiled.Events/EventArgs/Scp079/StartingSpeakerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp079/StartingSpeakerEventArgs.cs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/EXILED/Exiled.Events/EventArgs/Scp079/StoppingSpeakerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp079/StoppingSpeakerEventArgs.cs deleted file mode 100644 index e69de29bb2..0000000000 From 837340ff8768de996bb389e6e597163b04b46717 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:32:27 -0400 Subject: [PATCH 097/224] fix: WaypointToy causing pickup to grow (#611) * Semi-Fix * unnecessary using * Add TODO's * Fix NW SetParent * Final fix * Update SetParent calls to use worldPositionStays=true --------- Co-authored-by: Yamato --- .../Extensions/GameObjectExtensions.cs | 72 +++++++++++++++++++ EXILED/Exiled.API/Features/Items/Item.cs | 5 +- EXILED/Exiled.API/Features/Pickups/Pickup.cs | 6 +- 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 EXILED/Exiled.API/Extensions/GameObjectExtensions.cs diff --git a/EXILED/Exiled.API/Extensions/GameObjectExtensions.cs b/EXILED/Exiled.API/Extensions/GameObjectExtensions.cs new file mode 100644 index 0000000000..c935545b5d --- /dev/null +++ b/EXILED/Exiled.API/Extensions/GameObjectExtensions.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +#nullable enable +namespace Exiled.API.Extensions +{ + using UnityEngine; + + /// + /// A set of extensions for . + /// + public static class GameObjectExtensions + { + /// + /// Gets the global scale of a GameObject. + /// + /// The to check. + /// The global scale of the provided . + public static Vector3 GetWorldScale(this GameObject gameObject) + { + Transform? parent = gameObject.transform.parent; + + if (!parent) + { + /*Features.Log.Info($"[GetWorldScale:{new StackFrame(1).GetMethod().Name}] LocalScale:{gameObject.transform.localScale} LossyScale:{gameObject.transform.lossyScale} GlobalScale:{gameObject.transform.localScale}");*/ + return gameObject.transform.localScale; + } + + gameObject.transform.SetParent(null, true); + Vector3 value = gameObject.transform.localScale; + gameObject.transform.SetParent(parent, true); + + /*Features.Log.Info($"[GetWorldScale:{new StackFrame(1).GetMethod().Name}] LocalScale:{gameObject.transform.localScale} LossyScale:{gameObject.transform.lossyScale} GlobalScale:{value}");*/ + return value; + } + + /// + /// Sets the global scale of a GameObject. + /// + /// The to modify. + /// The new scale. + public static void SetWorldScale(this GameObject gameObject, Vector3 scale) + { + Transform? parent = gameObject.transform.parent; + + if (!parent) + { + gameObject.transform.localScale = scale; + /*Features.Log.Info($"[SetWorldScale] No Parent {scale}");*/ + return; + } + + /* + gameObject.transform.SetParent(null, true); + Vector3 value = gameObject.transform.localScale; + gameObject.transform.SetParent(parent, true); + + Features.Log.Info($"[SetWorldScale] Before: LocalScale:{gameObject.transform.localScale} LossyScale:{gameObject.transform.lossyScale} GlobalScale:{value}"); + */ + gameObject.transform.SetParent(null, true); + gameObject.transform.localScale = scale; + gameObject.transform.SetParent(parent, true); + /* + Features.Log.Info($"[SetWorldScale] After: LocalScale:{gameObject.transform.localScale} LossyScale:{gameObject.transform.lossyScale} GlobalScale:{value}"); + */ + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index c329b614ff..839585d0c9 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -10,6 +10,7 @@ namespace Exiled.API.Features.Items using System.Collections.Generic; using System.Linq; + using Exiled.API.Extensions; using Exiled.API.Features.Core; using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; @@ -438,6 +439,8 @@ internal virtual void ChangeOwner(Player oldOwner, Player newOwner) Base.OnAdded(null); } + // TODO: remove use of GetWorldScale after NW fix WaypointToy. + /// /// Helper method for saving data between items and pickups. /// @@ -451,7 +454,7 @@ internal virtual void ReadPickupInfoBefore(Pickup pickup) { if (pickup is not null) { - Scale = pickup.Scale; + Scale = pickup.GameObject.GetWorldScale(); } } diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index 5297acae5d..ab635fa81b 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -150,17 +150,17 @@ public ushort Serial /// public Vector3 Scale { - get => GameObject.transform.localScale; + get => GameObject.GetWorldScale(); set { if (!IsSpawned) { - GameObject.transform.localScale = value; + GameObject.SetWorldScale(value); return; } UnSpawn(); - GameObject.transform.localScale = value; + GameObject.SetWorldScale(value); Spawn(); } } From 0ec6164e8e78b1243f9350a8d72fc8151e460c85 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:32:49 +0300 Subject: [PATCH 098/224] feat: simplified addregeneration method (#610) Update Player.cs --- EXILED/Exiled.API/Features/Player.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 884230151f..d10642d407 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3514,6 +3514,13 @@ public void AddAhp(float amount, float limit = 75f, float decay = 1.2f, float ef .ServerAddProcess(amount, limit, decay, efficacy, sustain, persistant); } + /// + /// Adds a new to the player. + /// + /// Health points regenerated per second. + /// Total duration of the regeneration (in seconds). + public void AddRegeneration(float rate, float duration) => AddRegeneration(0, rate, duration, 1f, 1f); + /// /// Adds a new to the player. /// From 0fde095e787498f45213422e926da05681774e4e Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:33:05 -0400 Subject: [PATCH 099/224] fix: Add Player to TrackedPlayers before setting role (#606) the fix --- .../API/Features/CustomRole.cs | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 4838224e84..60c8dfdd1a 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -39,6 +39,9 @@ public abstract class CustomRole { private const float AddRoleDelay = 0.25f; + // used in AddRole and InternalChangingRole + private static bool skipChangingCheck; + private static Dictionary typeLookupTable = new(); private static Dictionary stringLookupTable = new(); @@ -515,19 +518,27 @@ public virtual void AddRole(Player player) if (Role != RoleTypeId.None) { - if (KeepPositionOnSpawn) + try { - if (KeepInventoryOnSpawn) - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None); + skipChangingCheck = true; + if (KeepPositionOnSpawn) + { + if (KeepInventoryOnSpawn) + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None); + else + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory); + } else - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory); + { + if (KeepInventoryOnSpawn && player.IsAlive) + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint); + else + player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All); + } } - else + finally { - if (KeepInventoryOnSpawn && player.IsAlive) - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint); - else - player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All); + skipChangingCheck = false; } } @@ -952,8 +963,10 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) 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 (!skipChangingCheck && ev.IsAllowed && ev.Reason != SpawnReason.Destroyed && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole))) RemoveRole(ev.Player); + else + skipChangingCheck = false; } private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) From 8a04f98cc97cc9529d00eb090ba4588670521fff Mon Sep 17 00:00:00 2001 From: Yamato Date: Wed, 6 Aug 2025 15:35:40 +0200 Subject: [PATCH 100/224] 9.7.2 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 762db082d6..fff0f24644 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.7.1 + 9.7.2 false From 6902b442d0fc5de3a6c1db2df313f492afaa4731 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 7 Aug 2025 18:58:10 +0200 Subject: [PATCH 101/224] DoorType Update --- EXILED/Exiled.API/Enums/DoorType.cs | 5 +++++ EXILED/Exiled.API/Features/Camera.cs | 2 ++ EXILED/Exiled.API/Features/Doors/Door.cs | 11 +++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index 4e3cb644f5..193ed9e65d 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -339,5 +339,10 @@ public enum DoorType /// Represents the HCZ_127_LAB door. /// Hcz127Lab, + + /// + /// Represents the door that have a window in it like the . + /// + HczWindowedDoor, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index da3f73e27b..cffea05fec 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -158,6 +158,8 @@ internal Camera(Scp079Camera camera079) Base = camera079; Camera079ToCamera.Add(camera079, this); Type = GetCameraType(); + if (Base != null && Type is CameraType.Unknown) + Log.Error($"[CameraType] Room: {Room?.Type ?? RoomType.Unknown} Name:{Name}"); } /// diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 7897d2dd94..ea920918fd 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -54,6 +54,8 @@ internal Door(DoorVariant door, List rooms) } Type = GetDoorType(); + if (Base != null && Type is DoorType.UnknownDoor or DoorType.UnknownGate or DoorType.UnknownElevator) + Log.Error($"[DoorType] Room: {Room?.Type ?? RoomType.Unknown} Name:{Name} GameObjectName:{GameObject.name}"); } /// @@ -608,7 +610,8 @@ private DoorType GetDoorType() "EZ BreakableDoor" => DoorType.EntranceDoor, "Prison BreakableDoor" => DoorType.PrisonDoor, "914 Door" => DoorType.Scp914Door, - "Intercom BreakableDoor" => Room?.Type switch + "EZ PortallessBreakableDoor" => DoorType.HczWindowedDoor, + "EZ Keycard BreakableDoor" => Room?.Type switch { RoomType.HczEzCheckpointA => DoorType.CheckpointArmoryA, RoomType.HczEzCheckpointB => DoorType.CheckpointArmoryB, @@ -621,15 +624,15 @@ private DoorType GetDoorType() 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 + "Cargo Elevator Door" => DoorType.ElevatorServerRoom, + "Nuke Elevator Door" => DoorType.ElevatorNuke, + "Elevator Door" or "Elevator Door 02" or "Elevator Door 01" => (Base as Interactables.Interobjects.ElevatorDoor)?.Group switch { ElevatorGroup.Scp049 => DoorType.ElevatorScp049, ElevatorGroup.GateB => DoorType.ElevatorGateB, ElevatorGroup.GateA => DoorType.ElevatorGateA, - ElevatorGroup.ServerRoom => DoorType.ElevatorServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => DoorType.ElevatorLczA, ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => DoorType.ElevatorLczB, - ElevatorGroup.Nuke01 or ElevatorGroup.Nuke02 => DoorType.ElevatorNuke, _ => DoorType.UnknownElevator, }, _ => DoorType.UnknownDoor, From 168d8efd4d5768aec9cd50f7eead3fe7fd9aa4b7 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 8 Aug 2025 00:56:47 +0200 Subject: [PATCH 102/224] Add `DoorType.Scp106Checkpoint` --- EXILED/Exiled.API/Enums/DoorType.cs | 7 ++++++- EXILED/Exiled.API/Features/Doors/Door.cs | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index 193ed9e65d..b5e0b61933 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -343,6 +343,11 @@ public enum DoorType /// /// Represents the door that have a window in it like the . /// - HczWindowedDoor, + HczServerRoomCloset, + + /// + /// Represents the checkpoint that handle door for and . + /// + Scp106Checkpoint, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index ea920918fd..dad2526d40 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -603,14 +603,19 @@ private DoorType GetDoorType() return doorName switch { - "LCZ PortallessBreakableDoor" => DoorType.Airlock, + "LCZ PortallessBreakableDoor" => Room?.Type switch + { + RoomType.Hcz106 => DoorType.Scp106Checkpoint, + RoomType.LczAirlock => DoorType.Airlock, + _ => DoorType.UnknownDoor, + }, "LCZ BreakableDoor" => DoorType.LightContainmentDoor, "HCZ BreakableDoor" => DoorType.HeavyContainmentDoor, "HCZ BulkDoor" => DoorType.HeavyBulkDoor, "EZ BreakableDoor" => DoorType.EntranceDoor, "Prison BreakableDoor" => DoorType.PrisonDoor, "914 Door" => DoorType.Scp914Door, - "EZ PortallessBreakableDoor" => DoorType.HczWindowedDoor, + "EZ PortallessBreakableDoor" => DoorType.HczServerRoomCloset, "EZ Keycard BreakableDoor" => Room?.Type switch { RoomType.HczEzCheckpointA => DoorType.CheckpointArmoryA, From 5e32ae660524caeaf58dcee547b7fb16495e7548 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 8 Aug 2025 08:50:37 +0200 Subject: [PATCH 103/224] Adding `LevelName` Getter to `Player` and `Room` --- EXILED/Exiled.API/Features/Player.cs | 7 +++++++ EXILED/Exiled.API/Features/Room.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index d10642d407..204f601198 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -43,6 +43,7 @@ namespace Exiled.API.Features using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp330; using MapGeneration.Distributors; + using MapGeneration.Rooms; using MEC; using Mirror; using Mirror.LiteNetLib4Mirror; @@ -1014,6 +1015,12 @@ public string GroupName /// public ZoneType Zone => CurrentRoom?.Zone ?? ZoneType.Unspecified; + /// + /// Gets the current Level the player is in. + /// + /// Will return null if CurrentRoom is not a . + public RoomLevelName? LevelName => CurrentRoom?.LevelName; + /// /// Gets the current the player is in. Can be . /// diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index e5bcc9c0d5..b472bf10f5 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -17,6 +17,7 @@ namespace Exiled.API.Features using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; using MapGeneration; + using MapGeneration.Rooms; using MEC; using Mirror; using PlayerRoles.PlayableScps.Scp079; @@ -76,6 +77,12 @@ public class Room : MonoBehaviour, IWorldSpace /// public RoomName RoomName => Identifier.Name; + /// + /// Gets the room's . + /// + /// Will return null if the Room is not a . + public RoomLevelName? LevelName => Identifier is MultiLevelRoomIdentifier multiLevelRoomIdentifier ? (RoomLevelName?)multiLevelRoomIdentifier.Name : null; + /// /// Gets the room's . /// From d784405b271aae24054721c613562eba62056846 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 8 Aug 2025 09:22:15 +0200 Subject: [PATCH 104/224] WaypointToy Wrappers --- EXILED/Exiled.API/Enums/AdminToyType.cs | 5 ++ EXILED/Exiled.API/Features/Toys/AdminToy.cs | 12 +++-- EXILED/Exiled.API/Features/Toys/Waypoint.cs | 60 +++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Toys/Waypoint.cs diff --git a/EXILED/Exiled.API/Enums/AdminToyType.cs b/EXILED/Exiled.API/Enums/AdminToyType.cs index 0ad8bb43dd..381e592f27 100644 --- a/EXILED/Exiled.API/Enums/AdminToyType.cs +++ b/EXILED/Exiled.API/Enums/AdminToyType.cs @@ -52,5 +52,10 @@ public enum AdminToyType /// Text toy. /// TextToy, + + /// + /// Waypoint toy. + /// + WaypointToy, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Toys/AdminToy.cs b/EXILED/Exiled.API/Features/Toys/AdminToy.cs index 4a82030c8c..8e83170121 100644 --- a/EXILED/Exiled.API/Features/Toys/AdminToy.cs +++ b/EXILED/Exiled.API/Features/Toys/AdminToy.cs @@ -8,14 +8,12 @@ namespace Exiled.API.Features.Toys { using System.Collections.Generic; - using System.Linq; using AdminToys; using Enums; using Exiled.API.Interfaces; using Footprinting; - using InventorySystem.Items; using Mirror; using UnityEngine; @@ -110,6 +108,13 @@ public Vector3 Scale get => AdminToyBase.transform.localScale; set { + // TODO: Remove this part of code when NW will have fix the issue + if (this is Waypoint) + { + if (value.x != value.y || value.y != value.z || value.sqrMagnitude >= Vector3.one.sqrMagnitude) + Log.Warn("NW WaypointToy have error when Scale is bigger than one or than x y z are not the same"); + } + AdminToyBase.transform.localScale = value; AdminToyBase.NetworkScale = value; } @@ -170,7 +175,8 @@ public static AdminToy Get(AdminToyBase adminToyBase) Scp079CameraToy scp079CameraToy => new CameraToy(scp079CameraToy), InvisibleInteractableToy invisibleInteractableToy => new InteractableToy(invisibleInteractableToy), TextToy textToy => new Text(textToy), - _ => throw new System.NotImplementedException() + WaypointToy waypointToy => new Waypoint(waypointToy), + _ => throw new System.NotImplementedException(), }; } diff --git a/EXILED/Exiled.API/Features/Toys/Waypoint.cs b/EXILED/Exiled.API/Features/Toys/Waypoint.cs new file mode 100644 index 0000000000..13dce85273 --- /dev/null +++ b/EXILED/Exiled.API/Features/Toys/Waypoint.cs @@ -0,0 +1,60 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Toys +{ + using AdminToys; + using Enums; + using Exiled.API.Interfaces; + using UnityEngine; + + /// + /// A wrapper class for . + /// + public class Waypoint : AdminToy, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The of the toy. + internal Waypoint(WaypointToy speakerToy) + : base(speakerToy, AdminToyType.WaypointToy) => Base = speakerToy; + + /// + /// Gets the prefab. + /// + public static WaypointToy Prefab => PrefabHelper.GetPrefab(PrefabType.WaypointToy); + + /// + /// Gets the base . + /// + public WaypointToy Base { get; } + + /// + /// Gets or sets the Waypoint shown. + /// + public float Priority + { + get => Base.Priority; + set => Base.Priority = value; + } + + /// + /// Gets or sets a value indicating whether the Bounds are shown for Debug. + /// + public bool VisualizeBounds + { + get => Base.NetworkVisualizeBounds; + set => Base.NetworkVisualizeBounds = value; + } + + /// + /// Gets the id of the Waypoint use for . + /// + public byte WaypointId => Base._waypointId; + } +} From 1328106f8cb1cf76f85815b0b093efcd0f39be7a Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 8 Aug 2025 09:25:36 +0200 Subject: [PATCH 105/224] Rename HczServerRoomCloset to ServerRoomCloset in DoorType Updated the DoorType enum to rename HczServerRoomCloset to ServerRoomCloset and clarified the summary to reference RoomType.HczServerRoom Closet. --- EXILED/Exiled.API/Enums/DoorType.cs | 4 ++-- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index b5e0b61933..c2a2008192 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -341,9 +341,9 @@ public enum DoorType Hcz127Lab, /// - /// Represents the door that have a window in it like the . + /// Represents the door in the Closet. /// - HczServerRoomCloset, + ServerRoomCloset, /// /// Represents the checkpoint that handle door for and . diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index dad2526d40..711e0bfadb 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -615,7 +615,7 @@ private DoorType GetDoorType() "EZ BreakableDoor" => DoorType.EntranceDoor, "Prison BreakableDoor" => DoorType.PrisonDoor, "914 Door" => DoorType.Scp914Door, - "EZ PortallessBreakableDoor" => DoorType.HczServerRoomCloset, + "EZ PortallessBreakableDoor" => DoorType.ServerRoomCloset, "EZ Keycard BreakableDoor" => Room?.Type switch { RoomType.HczEzCheckpointA => DoorType.CheckpointArmoryA, From e9929d8661464090418d347424f35f0f8e8fcc7a Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 8 Aug 2025 18:55:57 +0200 Subject: [PATCH 106/224] Update some NW fix and Add Heal door fix --- .../EventArgs/Player/DamagingDoorEventArgs.cs | 5 +- .../Patches/Events/Player/DamagingDoor.cs | 3 +- .../Patches/Fixes/Scp173FirstKillPatch.cs | 55 ------------------- .../Patches/Fixes/Scp173SecondKillPatch.cs | 54 ------------------ .../Patches/Fixes/Scp3114FriendlyFireFix.cs | 10 ++-- .../Patches/Fixes/WarheadConfigLockGateFix.cs | 45 --------------- 6 files changed, 9 insertions(+), 163 deletions(-) delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/WarheadConfigLockGateFix.cs diff --git a/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs index efa7229f24..c3e78bdfd6 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs @@ -31,6 +31,9 @@ public DamagingDoorEventArgs(DoorVariant door, float damage, DoorDamageType door Door = Door.Get(door); Damage = damage; DamageType = doorDamageType; + + // TODO: Remove when NW fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/817 + IsAllowed = damage > 0; } /// @@ -46,7 +49,7 @@ public DamagingDoorEventArgs(DoorVariant door, float damage, DoorDamageType door /// /// Gets or sets a value indicating whether the door can be broken. /// - public bool IsAllowed { get; set; } = true; + public bool IsAllowed { get; set; } /// /// Gets the dealt to the door. diff --git a/EXILED/Exiled.Events/Patches/Events/Player/DamagingDoor.cs b/EXILED/Exiled.Events/Patches/Events/Player/DamagingDoor.cs index 2f5135a56a..ee948e0dcd 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/DamagingDoor.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/DamagingDoor.cs @@ -25,9 +25,8 @@ namespace Exiled.Events.Patches.Events.Player /// /// Patch the . - /// Adds the event. + /// Adds the event and fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/817 in the EventArgs. /// - [EventPatch(typeof(Player), nameof(Player.DamagingDoor))] [HarmonyPatch(typeof(BreakableDoor), nameof(BreakableDoor.ServerDamage))] internal static class DamagingDoor { diff --git a/EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs b/EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs deleted file mode 100644 index 773050e16f..0000000000 --- a/EXILED/Exiled.Events/Patches/Fixes/Scp173FirstKillPatch.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using CustomPlayerEffects; - using HarmonyLib; - using PlayerRoles.PlayableScps.Scp173; - using UnityEngine; - - using static HarmonyLib.AccessTools; - - /// - /// Patches to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 bug. - /// - [HarmonyPatch(typeof(Scp173SnapAbility), nameof(Scp173SnapAbility.TryHitTarget))] - internal static class Scp173FirstKillPatch - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - Label continueLabel = generator.DefineLabel(); - - int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldc_I4_0); - newInstructions[index].WithLabels(continueLabel); - - newInstructions.InsertRange(index, new CodeInstruction[] - { - // if (hitboxIdentity.TargetHub.playerEffectController.GetEffect().IsEnabled) return false; - new(OpCodes.Ldloc_2), - new(OpCodes.Callvirt, PropertyGetter(typeof(HitboxIdentity), nameof(HitboxIdentity.TargetHub))), - new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.playerEffectsController))), - new(OpCodes.Callvirt, Method(typeof(PlayerEffectsController), nameof(PlayerEffectsController.GetEffect), generics: new[] { typeof(SpawnProtected) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(StatusEffectBase), nameof(StatusEffectBase.IsEnabled))), - new(OpCodes.Brfalse_S, continueLabel), - new(OpCodes.Ldc_I4_0), - new(OpCodes.Ret), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs b/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs deleted file mode 100644 index 8fd02372b3..0000000000 --- a/EXILED/Exiled.Events/Patches/Fixes/Scp173SecondKillPatch.cs +++ /dev/null @@ -1,54 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using CustomPlayerEffects; - using HarmonyLib; - using Mirror; - using PlayerRoles.PlayableScps.Scp173; - - using static HarmonyLib.AccessTools; - - /// - /// Patches to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 bug. - /// - [HarmonyPatch(typeof(Scp173TeleportAbility), nameof(Scp173TeleportAbility.ServerProcessCmd))] - internal static class Scp173SecondKillPatch - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - Label returnLabel = generator.DefineLabel(); - - newInstructions[newInstructions.Count - 1].WithLabels(returnLabel); - - int offset = -5; - int index = newInstructions.FindIndex(x => x.Is(OpCodes.Callvirt, Method(typeof(MovementTracer), nameof(MovementTracer.GenerateBounds)))) + offset; - - newInstructions.InsertRange(index, new[] - { - // if (hub.playerEffectController.GetEffect().IsEnabled) return; - new CodeInstruction(OpCodes.Ldloc_S, 5).MoveLabelsFrom(newInstructions[index]), - new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.playerEffectsController))), - new(OpCodes.Callvirt, Method(typeof(PlayerEffectsController), nameof(PlayerEffectsController.GetEffect), generics: new[] { typeof(SpawnProtected) })), - new(OpCodes.Callvirt, PropertyGetter(typeof(StatusEffectBase), nameof(StatusEffectBase.IsEnabled))), - new(OpCodes.Brtrue_S, returnLabel), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} diff --git a/EXILED/Exiled.Events/Patches/Fixes/Scp3114FriendlyFireFix.cs b/EXILED/Exiled.Events/Patches/Fixes/Scp3114FriendlyFireFix.cs index 6d0f4accf4..59767063fe 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/Scp3114FriendlyFireFix.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/Scp3114FriendlyFireFix.cs @@ -29,7 +29,7 @@ namespace Exiled.Events.Patches.Fixes /// Fix Throwing a ghostlight with Scp in the room stun 079. /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/55). /// - // [HarmonyPatch(typeof(Scp2176Projectile), nameof(Scp2176Projectile.ServerShatter))] + [HarmonyPatch(typeof(Scp2176Projectile), nameof(Scp2176Projectile.ServerShatter))] internal class Scp3114FriendlyFireFix { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) @@ -38,10 +38,10 @@ private static IEnumerable Transpiler(IEnumerable x.LoadsField(Field(typeof(RoomLightController), nameof(RoomLightController.Instances)))) + offset; + int offset = 4; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldsfld) + offset; - Label skip = newInstructions[index].labels[0]; + Label skip = (Label)newInstructions[index].operand; offset = -4; index += offset; @@ -57,8 +57,6 @@ private static IEnumerable Transpiler(IEnumerable -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using Footprinting; - using HarmonyLib; - using Interactables.Interobjects.DoorUtils; - using InventorySystem; - using InventorySystem.Items.Firearms.Ammo; - using InventorySystem.Items.Pickups; - - using static HarmonyLib.AccessTools; - - /// - /// Patches delegate. - /// Fix than NW config "lock_gates_on_countdown" - /// reported https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/316. - /// - [HarmonyPatch(typeof(DoorEventOpenerExtension), nameof(DoorEventOpenerExtension.Trigger))] - internal class WarheadConfigLockGateFix - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - // replace Contains with StartWith - int index = newInstructions.FindIndex(x => x.operand == (object)Method(typeof(string), nameof(string.Contains), new[] { typeof(string) })); - newInstructions[index].operand = Method(typeof(string), nameof(string.StartsWith), new System.Type[] { typeof(string) }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} From 20edebca77ec9c34a428a57efa9a9096272337e3 Mon Sep 17 00:00:00 2001 From: Banalny-Banan <133122450+Banalny-Banan@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:39:08 +0300 Subject: [PATCH 107/224] feat: add `Player.Enumerable` and `Player.Count` properties (#616) add Player.Enumerable and Player.Count properties --- EXILED/Exiled.API/Features/Player.cs | 13 +++++++++++++ EXILED/Exiled.API/Features/Server.cs | 7 ++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 204f601198..0f189f1e28 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -138,6 +138,19 @@ public Player(GameObject gameObject) /// public static IReadOnlyCollection List => Dictionary.Values.ToList(); + /// + /// Gets an of all 's on the server. + /// This property should be used for enumeration (e.g. LINQ) as it doesn't create a new list, improving performance. + /// + public static IEnumerable Enumerable => Dictionary.Values; + + /// + /// Gets the number of players currently on the server. + /// + /// + /// + public static int Count => Dictionary.Count; + /// /// Gets a containing cached and their user ids. /// diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 8cd65dc079..e07764d9e9 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -143,11 +143,8 @@ public static bool FriendlyFire } } - /// - /// Gets the number of players currently on the server. - /// - /// - public static int PlayerCount => Player.Dictionary.Count; + /// + public static int PlayerCount => Player.Count; /// /// Gets or sets the maximum number of players able to be on the server. From 8ac2a4170f2ab5f05acd5a53b624d51c6648873e Mon Sep 17 00:00:00 2001 From: Snivy Films <120346554+SnivyFilms@users.noreply.github.com> Date: Sat, 9 Aug 2025 09:46:44 -0400 Subject: [PATCH 108/224] feat: Custom Weapon Attachments additions. (#521) * Fixes the Frenchie's English * New Custom Stuff (Untested) * Corrected Attachment Changing * Minor fix --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../API/Features/CustomWeapon.cs | 19 +++++++++++++++++++ .../API/Features/CustomRole.cs | 10 +++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs index c1c049876d..733e6317d2 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs @@ -16,6 +16,7 @@ namespace Exiled.CustomItems.API.Features using Exiled.API.Features.DamageHandlers; using Exiled.API.Features.Items; using Exiled.API.Features.Pickups; + using Exiled.Events.EventArgs.Item; using Exiled.Events.EventArgs.Player; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; @@ -150,6 +151,7 @@ protected override void SubscribeEvents() Exiled.Events.Handlers.Player.Shooting += OnInternalShooting; Exiled.Events.Handlers.Player.Shot += OnInternalShot; Exiled.Events.Handlers.Player.Hurting += OnInternalHurting; + Exiled.Events.Handlers.Item.ChangingAttachments += OnInternalChangingAttachment; base.SubscribeEvents(); } @@ -162,6 +164,7 @@ protected override void UnsubscribeEvents() Exiled.Events.Handlers.Player.Shooting -= OnInternalShooting; Exiled.Events.Handlers.Player.Shot -= OnInternalShot; Exiled.Events.Handlers.Player.Hurting -= OnInternalHurting; + Exiled.Events.Handlers.Item.ChangingAttachments -= OnInternalChangingAttachment; base.UnsubscribeEvents(); } @@ -208,6 +211,14 @@ protected virtual void OnHurting(HurtingEventArgs ev) ev.Amount = Damage; } + /// + /// Handles attachment changing for custom weapons. + /// + /// . + protected virtual void OnChangingAttachment(ChangingAttachmentsEventArgs ev) + { + } + private void OnInternalReloading(ReloadingWeaponEventArgs ev) { if (!Check(ev.Player.CurrentItem)) @@ -320,5 +331,13 @@ private void OnInternalHurting(HurtingEventArgs ev) OnHurting(ev); } + + private void OnInternalChangingAttachment(ChangingAttachmentsEventArgs ev) + { + if (!Check(ev.Player.CurrentItem)) + return; + + OnChangingAttachment(ev); + } } } \ 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 60c8dfdd1a..5b226ca2d2 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -17,6 +17,7 @@ namespace Exiled.CustomRoles.API.Features using Exiled.API.Extensions; using Exiled.API.Features; using Exiled.API.Features.Attributes; + using Exiled.API.Features.Items; using Exiled.API.Features.Pools; using Exiled.API.Features.Roles; using Exiled.API.Features.Spawn; @@ -558,7 +559,14 @@ public virtual void AddRole(Player player) foreach (string itemName in Inventory) { Log.Debug($"{Name}: Adding {itemName} to inventory."); - TryAddItem(player, itemName); + if (TryAddItem(player, itemName) && CustomItem.TryGet(itemName, out CustomItem? customItem) && customItem is CustomWeapon customWeapon) + { + if (player.CurrentItem is Firearm firearm && !customWeapon.Attachments.IsEmpty()) + { + firearm.AddAttachment(customWeapon.Attachments); + Log.Debug($"{Name}: Applied attachments to {itemName}."); + } + } } if (Ammo.Count > 0) From 603e46e9624b437e7b0139f96bf4dd5b024dd781 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:46:57 +0300 Subject: [PATCH 109/224] feat: Ignored players removed from the Round Targets (#614) * Create RoundTargetCount.cs * Modify TargetMethod() to My way :3 * fix variable Name --------- Co-authored-by: Yamato Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Patches/Generic/RoundTargetCount.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 EXILED/Exiled.Events/Patches/Generic/RoundTargetCount.cs diff --git a/EXILED/Exiled.Events/Patches/Generic/RoundTargetCount.cs b/EXILED/Exiled.Events/Patches/Generic/RoundTargetCount.cs new file mode 100644 index 0000000000..a4d67081e9 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/RoundTargetCount.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the Propperty. + /// + [HarmonyPatch] + internal class RoundTargetCount + { +#pragma warning disable SA1600 // Elements should be documented + public static Type PrivateType { get; internal set; } + + private static MethodInfo TargetMethod() + { + PrivateType = typeof(RoundSummary).GetNestedTypes(all) + .FirstOrDefault(currentType => currentType.Name is "<>c"); + if (PrivateType == null) + throw new Exception("State machine type for <>c not found."); + MethodInfo updateTargetCountFunction = PrivateType.GetMethods(all).FirstOrDefault(x => x.Name.Contains("UpdateTargetCount")); + + if (updateTargetCountFunction == null) + throw new Exception("UpdateTargetCount method not found in the state machine type."); + return updateTargetCountFunction; + } + + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label skip = generator.DefineLabel(); + + newInstructions[0].labels.Add(skip); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // if (Round.IgnoredPlayers.Contains(hub)) + // return false; + new(OpCodes.Call, PropertyGetter(typeof(Round), nameof(Round.IgnoredPlayers))), + new(OpCodes.Ldarg_1), + new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Contains))), + new(OpCodes.Brfalse_S, skip), + + new(OpCodes.Ldc_I4_0), + new(OpCodes.Ret), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} From dd28333f666b1237e52b7b00cd449e59264d8593 Mon Sep 17 00:00:00 2001 From: sky <99112969+skyfr0676@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:47:33 +0200 Subject: [PATCH 110/224] feat: Just adding an event i've made from apis-rework (#498) * tkt pas * oopsie * i'm dumb * fix * oups * oups --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Scp173/AddingObserverEventArgs.cs | 51 ++++++++ .../Scp173/RemovedObserverEventArgs.cs | 44 +++++++ EXILED/Exiled.Events/Handlers/Scp173.cs | 22 ++++ .../Patches/Events/Scp173/Observers.cs | 117 ++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp173/AddingObserverEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp173/RemovedObserverEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp173/Observers.cs diff --git a/EXILED/Exiled.Events/EventArgs/Scp173/AddingObserverEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp173/AddingObserverEventArgs.cs new file mode 100644 index 0000000000..b47ee821c6 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp173/AddingObserverEventArgs.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp173 +{ + using Exiled.API.Features; + using Exiled.API.Features.Roles; + using Interfaces; + + /// + /// Contains all information before a player sees SCP-173. + /// + public class AddingObserverEventArgs : IScp173Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public AddingObserverEventArgs(Player player, Player observer, bool isAllowed = true) + { + Scp173 = player.Role.As(); + Player = player; + Observer = observer; + IsAllowed = isAllowed; + } + + /// + public Scp173Role Scp173 { get; } + + /// + /// Gets the target who has looked at SCP-173. + /// + public Player Observer { get; } + + /// + /// Gets the player who's controlling SCP-173. + /// + public Player Player { get; } + + /// + /// Gets or sets a value indicating whether the player can be added as an observer. + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp173/RemovedObserverEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp173/RemovedObserverEventArgs.cs new file mode 100644 index 0000000000..a941cfeea6 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp173/RemovedObserverEventArgs.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.Scp173 +{ + using Exiled.API.Features; + using Exiled.API.Features.Roles; + using Interfaces; + + /// + /// Contains all information after a player stops looking at SCP-173. + /// + public class RemovedObserverEventArgs : IScp173Event + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public RemovedObserverEventArgs(Player player, Player observer) + { + Scp173 = player.Role.As(); + Player = player; + Observer = observer; + } + + /// + public Scp173Role Scp173 { get; } + + /// + /// Gets the player who's controlling SCP-173. + /// + public Player Player { get; } + + /// + /// Gets the target who no longer sees SCP-173. + /// + public Player Observer { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Scp173.cs b/EXILED/Exiled.Events/Handlers/Scp173.cs index ea6e0311ea..b5d577d5cd 100644 --- a/EXILED/Exiled.Events/Handlers/Scp173.cs +++ b/EXILED/Exiled.Events/Handlers/Scp173.cs @@ -42,6 +42,16 @@ public static class Scp173 /// public static Event BeingObserved { get; set; } = new(); + /// + /// Invoked before a player starts looking at SCP-173. + /// + public static Event AddingObserver { get; set; } = new(); + + /// + /// Invoked after a player stops looking at SCP-173. + /// + public static Event RemovedObserver { get; set; } = new(); + /// /// Called before players near SCP-173 blink. /// @@ -71,5 +81,17 @@ public static class Scp173 /// /// The instance. public static void OnBeingObserved(BeingObservedEventArgs ev) => BeingObserved.InvokeSafely(ev); + + /// + /// Called before player starts looking at SCP-173. + /// + /// The instance. + public static void OnAddingObserver(AddingObserverEventArgs ev) => AddingObserver.InvokeSafely(ev); + + /// + /// Called after a player stops looking at SCP-173. + /// + /// The instance. + public static void OnRemovedObserver(RemovedObserverEventArgs ev) => RemovedObserver.InvokeSafely(ev); } } diff --git a/EXILED/Exiled.Events/Patches/Events/Scp173/Observers.cs b/EXILED/Exiled.Events/Patches/Events/Scp173/Observers.cs new file mode 100644 index 0000000000..4f3cee3a94 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp173/Observers.cs @@ -0,0 +1,117 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp173 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features; + using Attributes; + using Exiled.API.Features.Pools; + using Exiled.Events.EventArgs.Scp173; + using HarmonyLib; + using PlayerRoles.PlayableScps.Scp173; + using PlayerRoles.Subroutines; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Scp173), nameof(Handlers.Scp173.AddingObserver))] + [EventPatch(typeof(Handlers.Scp173), nameof(Handlers.Scp173.RemovedObserver))] + [HarmonyPatch(typeof(Scp173ObserversTracker), nameof(Scp173ObserversTracker.UpdateObserver))] + internal static class Observers + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + // AddingObserver patch + Label returnLabel = generator.DefineLabel(); + LocalBuilder ev = generator.DeclareLocal(typeof(AddingObserverEventArgs)); + + int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(HashSet), nameof(HashSet.Add)))) + 2; + newInstructions[index].labels.Add(returnLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // Player.Get(Owner); + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(ply); + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // true + new(OpCodes.Ldc_I4_1), + + // AddingObserverEventArgs ev = new(Player.Get(Owner), Player.Get(ply), true); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(AddingObserverEventArgs))[0]), + new(OpCodes.Stloc, ev), + + // Scp173.OnAddingObserver(ev); + new(OpCodes.Ldloc, ev), + new(OpCodes.Call, Method(typeof(Handlers.Scp173), nameof(Handlers.Scp173.OnAddingObserver))), + + // if (!ev.IsAllowed) + new(OpCodes.Ldloc, ev), + new(OpCodes.Callvirt, + PropertyGetter(typeof(AddingObserverEventArgs), nameof(AddingObserverEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, returnLabel), + + // Observers.Remove(ply); + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(Scp173ObserversTracker), nameof(Scp173ObserversTracker.Observers))), + new(OpCodes.Ldarg_1), + new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Remove))), + new(OpCodes.Pop), + + // return 0; + new(OpCodes.Ldc_I4_0), + new(OpCodes.Ret), + }); + + // RemoveObserver patch + int index2 = newInstructions.FindLastIndex(x => x.Calls(Method(typeof(HashSet), nameof(HashSet.Remove)))) + 2; + + LocalBuilder ev2 = generator.DeclareLocal(typeof(RemovedObserverEventArgs)); + + newInstructions.InsertRange(index2, new CodeInstruction[] + { + // Player.Get(Owner); + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, + PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(ply); + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // new RemovedObserverEventArgs(Player.Get(Owner), Player.Get(ply)); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovedObserverEventArgs))[0]), + new(OpCodes.Stloc, ev2), + + // Scp173.OnRemovingObserver(new RemovedObserverEventArgs(Player.Get(Owner), Player.Get(ply))); + new(OpCodes.Ldloc, ev2), + new(OpCodes.Call, Method(typeof(Handlers.Scp173), nameof(Handlers.Scp173.OnRemovedObserver))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + { + yield return newInstructions[z]; + } + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From 3bdef9239ffdf4a9f008168bbb35e7b6524bfa5b Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:49:25 +0300 Subject: [PATCH 111/224] feat: Clear Changed Ratio event patch (#613) * clean.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs * Update ChangedAspectRatio.cs --------- Co-authored-by: Yamato --- .../Events/Player/ChangedAspectRatio.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs index 8a12f60f94..1cc7dfc8a9 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs @@ -12,6 +12,7 @@ namespace Exiled.Events.Patches.Events.Player using API.Features.Pools; using CentralAuth; + using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Player; using HarmonyLib; using UnityEngine; @@ -22,6 +23,7 @@ namespace Exiled.Events.Patches.Events.Player /// Patches . /// Adds the event. /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ChangedRatio))] [HarmonyPatch(typeof(AspectRatioSync), nameof(AspectRatioSync.UserCode_CmdSetAspectRatio__Single))] internal class ChangedAspectRatio { @@ -29,17 +31,17 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - LocalBuilder oldratio = generator.DeclareLocal(typeof(float)); + 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; + // float oldRatio = this.AspectRatio; new(OpCodes.Ldarg_0), new(OpCodes.Callvirt, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), - new(OpCodes.Stloc_S, oldratio.LocalIndex), + new(OpCodes.Stloc_S, oldRatio.LocalIndex), }); int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ret); @@ -60,14 +62,13 @@ private static IEnumerable Transpiler(IEnumerable Date: Sat, 9 Aug 2025 23:52:38 +0300 Subject: [PATCH 112/224] feat: Add Saving By Anti207 event (#617) * Create SavingByAntiScp207EventArgs.cs * Update Player.cs * Create SavingByAntiScp207.cs --------- Co-authored-by: Yamato --- .../Player/SavingByAntiScp207EventArgs.cs | 74 ++++++++++++++ EXILED/Exiled.Events/Handlers/Player.cs | 11 +++ .../Events/Player/SavingByAntiScp207.cs | 96 +++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs diff --git a/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs new file mode 100644 index 0000000000..71cd25905b --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs @@ -0,0 +1,74 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using CustomPlayerEffects; + using Exiled.API.Features; + using Exiled.API.Features.DamageHandlers; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information before a player is saved from death by the Anti-SCP-207 effect. + /// + public class SavingByAntiScp207EventArgs : IPlayerEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who is being saved. + /// The amount of damage that would have been applied. + /// The damage handler that describes the damage. + /// The hitbox that was hit. + public SavingByAntiScp207EventArgs(ReferenceHub player, float damageAmount, DamageHandlerBase handler, HitboxType hitboxType) + { + Player = Player.Get(player); + + Handler = handler; + HitboxType = hitboxType; + DamageAmount = damageAmount; + DamageMultiplier = (Player.Health + Player.ArtificialHealth - AntiScp207.DeathSaveHealth) / damageAmount; + IsAllowed = true; + } + + /// + /// Gets the player who is being saved. + /// + public Player Player { get; } + + /// + /// Gets the amount of damage that would have been applied. + /// + public float DamageAmount { get; } + + /// + /// Gets or sets the multiplier for the damage that is applied when the event is allowed. + /// + public float DamageMultiplier { get; set; } + + /// + /// Gets or sets the multiplier for the damage that if event denied. + /// + public float DeniedDamageMultiplier { get; set; } = 1; + + /// + /// Gets the damage handler that describes the incoming damage. + /// + public DamageHandlerBase Handler { get; } + + /// + /// Gets the hitbox that was hit. + /// + public HitboxType HitboxType { get; } + + /// + /// Gets or sets a value indicating whether the event is allowed. + /// If set to false, the event will be denied. + /// + public bool IsAllowed { get; set; } + } +} diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index 7037747e7e..590e4924d1 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -113,6 +113,11 @@ public class Player /// public static Event Interacted { get; set; } = new(); + /// + /// Invoked before a is saved from death by the Anti-SCP-207 effect. + /// + public static Event SavingByAntiScp207 { get; set; } = new(); + /// /// Invoked before spawning a . /// @@ -1239,6 +1244,12 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item /// The instance. public static void OnHealed(HealedEventArgs ev) => Healed.InvokeSafely(ev); + /// + /// Called before a is saved from death by the Anti-SCP-207 effect. + /// + /// The instance. + public static void OnSavingByAntiScp207(SavingByAntiScp207EventArgs ev) => SavingByAntiScp207.InvokeSafely(ev); + /// /// Called before a dies. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs new file mode 100644 index 0000000000..e3275e0a95 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs @@ -0,0 +1,96 @@ +// ----------------------------------------------------------------------- +// +// 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 CustomPlayerEffects; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event before the player is saved from Anti-SCP-207 damage. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.SavingByAntiScp207))] + [HarmonyPatch(typeof(AntiScp207), nameof(AntiScp207.GetDamageModifier))] + internal class SavingByAntiScp207 + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(SavingByAntiScp207EventArgs)); + + int offset = -1; + int index = newInstructions.FindLastIndex(i => i.Calls(Method(typeof(StatusEffectBase), nameof(StatusEffectBase.DisableEffect)))) + offset; + + Label skipLabel = generator.DefineLabel(); + Label gotoEventLabel = newInstructions[index].labels[0]; + + newInstructions[index].labels = new List
Scp106Checkpoint, + + /// + /// Represents the door in the . + /// + TestRoom, + + /// + /// Represents the door in the Closet. + /// + PlantsCloset, + + /// + /// Represents the door used for Checkpoint. + /// + Checkpoint, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index cffea05fec..e38b7abb35 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -120,6 +120,7 @@ public class Camera : IWrapper, IWorldSpace ["TUNNEL ENTRANCE"] = CameraType.TunnelEntrance, // new + ["HCZ CURVE"] = CameraType.HczCurve, ["JUNK MAIN"] = CameraType.HczJunkMain, ["JUNK HALLWAY"] = CameraType.HczJunkHallway, ["CORNER DEEP"] = CameraType.HczCornerDeep, diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 711e0bfadb..52bc80b55a 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -606,7 +606,10 @@ private DoorType GetDoorType() "LCZ PortallessBreakableDoor" => Room?.Type switch { RoomType.Hcz106 => DoorType.Scp106Checkpoint, + RoomType.HczTestRoom => DoorType.TestRoom, + RoomType.LczPlants => DoorType.PlantsCloset, RoomType.LczAirlock => DoorType.Airlock, + RoomType.HczEzCheckpointA or RoomType.HczEzCheckpointB or RoomType.LczCheckpointA or RoomType.LczCheckpointB => DoorType.Checkpoint, _ => DoorType.UnknownDoor, }, "LCZ BreakableDoor" => DoorType.LightContainmentDoor, From a393c8feadcebcbbab520e0ce81478e9a20a1eb3 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:18:53 +0300 Subject: [PATCH 115/224] feat: Fake effect extensions (#618) * Update MirrorExtensions.cs * Update MirrorExtensions.cs * IndexOf is better --------- Co-authored-by: Yamato --- .../Exiled.API/Extensions/MirrorExtensions.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 1ab332af2a..a06cd5c294 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -16,6 +16,7 @@ namespace Exiled.API.Extensions using System.Text; using AudioPooling; + using CustomPlayerEffects; using Exiled.API.Enums; using Exiled.API.Features.Items; using Features; @@ -342,6 +343,64 @@ public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumer player.Position += Vector3.up * 0.25f; } + /// + /// Resynchronizes a specific effect from the effect owner to the target player. + /// + /// The player who owns the effect to be resynchronized. + /// The target player to whom the effect will be resynchronized. + /// The type of effect to be resynchronized. + public static void ResyncEffectTo(this Player effectOwner, Player target, EffectType effect) => effectOwner.SendFakeEffectTo(target, effect, effectOwner.GetEffect(effect).Intensity); + + /// + /// Resynchronizes a specific effect from the effect owner to the target players. + /// + /// The player who owns the effect to be resynchronized. + /// The list of target players to whom the effect will be resynchronized. + /// The type of effect to be resynchronized. + public static void ResyncEffectTo(this Player effectOwner, IEnumerable targets, EffectType effect) => effectOwner.SendFakeEffectTo(targets, effect, effectOwner.GetEffect(effect).Intensity); + + /// + /// Sends a fake effect to a list of target players, simulating the effect as if it originated from the effect owner. + /// + /// The player who owns the effect. + /// The list of target players to whom the effect will be sent. + /// The type of effect to be sent. + /// The intensity of the effect. + public static void SendFakeEffectTo(this Player effectOwner, IEnumerable targets, EffectType effect, byte intensity) + { + foreach (Player target in targets) + { + effectOwner.SendFakeEffectTo(target, effect, intensity); + } + } + + /// + /// Sends a fake effect to a target player, simulating the effect as if it originated from the effect owner. + /// + /// The player who owns the effect. + /// The target player to whom the effect will be sent. + /// The type of effect to be sent. + /// The intensity of the effect. + public static void SendFakeEffectTo(this Player effectOwner, Player target, EffectType effect, byte intensity) + { + SendFakeSyncObject(target, effectOwner.NetworkIdentity, typeof(PlayerEffectsController), (writer) => + { + StatusEffectBase foundEffect = effectOwner.GetEffect(effect); + int foundIndex = effectOwner.ReferenceHub.playerEffectsController.AllEffects.IndexOf(foundEffect); + if (foundIndex == -1) + { + Log.Error($"Effect {effect} not found in {effectOwner.Nickname}'s effects list."); + return; + } + + writer.WriteULong(0b0001); + writer.WriteUInt(1); + writer.WriteByte((byte)SyncList.Operation.OP_SET); + writer.WriteUInt((uint)foundIndex); + writer.WriteByte(intensity); + }); + } + /// /// Send CASSIE announcement that only can hear. /// From 8fbf2592e3f96102e28feefa58b40d6983222688 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:19:19 +0300 Subject: [PATCH 116/224] feat: Firearm new module & Reload Unload methods (#621) * Update Player.cs * Update Firearm.cs * validation for nre * Add validation for nre * Update Firearm.cs * Update Firearm.cs --- EXILED/Exiled.API/Features/Items/Firearm.cs | 93 +++++++++++++++------ EXILED/Exiled.API/Features/Player.cs | 66 +++++++++++++-- 2 files changed, 126 insertions(+), 33 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index eeee66adcd..46d544eed7 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -21,21 +21,15 @@ namespace Exiled.API.Features.Items using Exiled.API.Interfaces; using Exiled.API.Structs; using Extensions; - using InventorySystem; - using InventorySystem.Items; using InventorySystem.Items.Autosync; - using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; - using InventorySystem.Items.Firearms.BasicMessages; using InventorySystem.Items.Firearms.Modules; - using InventorySystem.Items.Pickups; - using MEC; - using UnityEngine; + + using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; using FirearmPickup = Pickups.FirearmPickup; - using Object = UnityEngine.Object; /// /// A wrapper class for . @@ -63,20 +57,26 @@ public Firearm(BaseFirearm itemBase) foreach (ModuleBase module in Base.Modules) { - if (module is IPrimaryAmmoContainerModule primaryAmmoModule) + switch (module) { - PrimaryMagazine ??= (PrimaryMagazine)Magazine.Get(primaryAmmoModule); - continue; - } + case IPrimaryAmmoContainerModule primaryAmmoModule: + PrimaryMagazine ??= (PrimaryMagazine)Magazine.Get(primaryAmmoModule); + break; - if (module is IAmmoContainerModule ammoModule) - { - BarrelMagazine ??= (BarrelMagazine)Magazine.Get(ammoModule); - } + case IAmmoContainerModule ammoModule: + BarrelMagazine ??= (BarrelMagazine)Magazine.Get(ammoModule); + break; - if (module is HitscanHitregModuleBase hitregModule) - { - HitscanHitregModule = hitregModule; + case HitscanHitregModuleBase hitregModule: + HitscanHitregModule = hitregModule; + break; + + case AnimatorReloaderModuleBase animatorReloaderModule: + AnimatorReloaderModule = animatorReloaderModule; + break; + + default: + break; } } } @@ -146,6 +146,11 @@ public static IReadOnlyDictionary public HitscanHitregModuleBase HitscanHitregModule { get; } + /// + /// Gets an animator reloader module for the current firearm. + /// + public AnimatorReloaderModuleBase AnimatorReloaderModule { get; } + /// /// Gets or sets the amount of ammo in the firearm magazine. /// @@ -713,12 +718,52 @@ public void ClearPreferences() /// /// For specific reloading logic you also can use for avaible weapons. /// - public void Reload() + public void ForceReload() { - if (Base.TryGetModule(out AnimatorReloaderModuleBase module)) - { - module.StartReloading(); - } + if (AnimatorReloaderModule == null) + return; + + AnimatorReloaderModule.IsReloading = true; + AnimatorReloaderModule.SendRpcHeaderWithRandomByte(ReloaderMessageHeader.Reload); + } + + /// + /// Attempts to reload the firearm with server-side validation. + /// + /// if the firearm was successfully reloaded. Otherwise, . + public bool Reload() + { + if (AnimatorReloaderModule == null) + return false; + + return AnimatorReloaderModule.ServerTryReload(); + } + + /// + /// Attempts to unload the firearm with server-side validation. + /// + /// if the firearm was successfully unload. Otherwise, . + public bool Unload() + { + if (AnimatorReloaderModule == null) + return false; + + return AnimatorReloaderModule.ServerTryUnload(); + } + + /// + /// Forces the firearm's client-side unload animation, bypassing server-side checks. + /// + /// + /// This only plays the animation and is not guaranteed to result in a successful unload. For server-validated unloading, use . + /// + public void ForceUnload() + { + if (AnimatorReloaderModule == null) + return; + + AnimatorReloaderModule.IsUnloading = true; + AnimatorReloaderModule.SendRpcHeaderWithRandomByte(ReloaderMessageHeader.Unload); } /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 0f189f1e28..09d591e594 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -64,6 +64,7 @@ namespace Exiled.API.Features using VoiceChat.Playbacks; using static DamageHandlers.DamageHandlerBase; + using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using DamageHandlerBase = PlayerStatsSystem.DamageHandlerBase; using Firearm = Items.Firearm; @@ -1803,23 +1804,70 @@ public void TrySetCustomRoleFriendlyFire(string roleTypeId, Dictionary Whether the item was able to be added. public bool TryRemoveCustomeRoleFriendlyFire(string role) => CustomRoleFriendlyFireMultiplier.Remove(role); + /// + /// Forces the player's client to play the weapon reload animation, bypassing server-side checks. + /// + /// if the command to start reloading was sent. Otherwise, . + /// + /// This method does not check if the weapon can actually be reloaded. It only forces the animation and is not guaranteed to result in a successful reload. + /// + public bool ForceReloadWeapon() + { + if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) + { + return false; + } + + firearm.AnimatorReloaderModule.IsReloading = true; + firearm.AnimatorReloaderModule.SendRpcHeaderWithRandomByte(ReloaderMessageHeader.Reload); + return true; + } + /// /// Forces the player to reload their current weapon. /// - /// if firearm was successfully reloaded. Otherwise, . + /// if the firearm was successfully reloaded. Otherwise, . public bool ReloadWeapon() { - if (CurrentItem is Firearm firearm) + if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) { - // TODO not finish - /* - bool result = firearm.Base.Ammo.ServerTryReload(); - Connection.Send(new RequestMessage(firearm.Serial, RequestType.Reload)); - return result; - */ + return false; } - return false; + return firearm.AnimatorReloaderModule.ServerTryReload(); + } + + /// + /// Forces the player's client to play the weapon unload animation, bypassing server-side checks. + /// + /// if the command to start unloading was sent. Otherwise, . + /// + /// This method does not check if the weapon can actually be unloaded. It only forces the animation and is not guaranteed to result in a successful unload. + /// + public bool ForceUnloadWeapon() + { + if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) + { + return false; + } + + firearm.AnimatorReloaderModule.IsUnloading = true; + firearm.AnimatorReloaderModule.SendRpcHeaderWithRandomByte(ReloaderMessageHeader.Unload); + return true; + } + + /// + /// Forces the player to unload their current weapon. + /// + /// if the firearm was successfully unloaded. Otherwise, . + public bool UnloadWeapon() + { + if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) + { + return false; + } + + return firearm.AnimatorReloaderModule.ServerTryUnload(); } /// From 1fb391d256a8a36d5f1d13968fdf7b6bf2cfe287 Mon Sep 17 00:00:00 2001 From: Yamato Date: Mon, 11 Aug 2025 21:34:50 +0200 Subject: [PATCH 117/224] Use target-typed new for RagdollData instantiation Replaces explicit type with target-typed 'new' when creating a new RagdollData instance in the CreationTime setter for improved code readability. --- EXILED/Exiled.API/Features/Ragdoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Ragdoll.cs b/EXILED/Exiled.API/Features/Ragdoll.cs index 51878021cf..7f0b05a415 100644 --- a/EXILED/Exiled.API/Features/Ragdoll.cs +++ b/EXILED/Exiled.API/Features/Ragdoll.cs @@ -180,7 +180,7 @@ public DateTime CreationTime set { float creationTime = (float)(NetworkTime.time - (DateTime.Now - value).TotalSeconds); - NetworkInfo = new RagdollData(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, creationTime); + NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, creationTime); } } From 7bd041ee1ec965f78cd5e57b4c94b27669f2dea6 Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 12 Aug 2025 13:09:39 +0200 Subject: [PATCH 118/224] 9.8.0 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index fff0f24644..fe7f5b2651 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.7.2 + 9.8.0 false From 5e83b644074149e33daf3a2dce01741b8e23946a Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 12 Aug 2025 13:35:22 +0200 Subject: [PATCH 119/224] Remove breaking change made in (#621) --- EXILED/Exiled.API/Features/Items/Firearm.cs | 8 ++++---- EXILED/Exiled.API/Features/Player.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 46d544eed7..576ebf78ae 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -718,7 +718,7 @@ public void ClearPreferences() /// /// For specific reloading logic you also can use for avaible weapons. /// - public void ForceReload() + public void Reload() { if (AnimatorReloaderModule == null) return; @@ -731,7 +731,7 @@ public void ForceReload() /// Attempts to reload the firearm with server-side validation. /// /// if the firearm was successfully reloaded. Otherwise, . - public bool Reload() + public bool TryReload() { if (AnimatorReloaderModule == null) return false; @@ -743,7 +743,7 @@ public bool Reload() /// Attempts to unload the firearm with server-side validation. /// /// if the firearm was successfully unload. Otherwise, . - public bool Unload() + public bool TryUnload() { if (AnimatorReloaderModule == null) return false; @@ -757,7 +757,7 @@ public bool Unload() /// /// This only plays the animation and is not guaranteed to result in a successful unload. For server-validated unloading, use . /// - public void ForceUnload() + public void Unload() { if (AnimatorReloaderModule == null) return; diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 09d591e594..960171e036 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1811,7 +1811,7 @@ public void TrySetCustomRoleFriendlyFire(string roleTypeId, Dictionary /// This method does not check if the weapon can actually be reloaded. It only forces the animation and is not guaranteed to result in a successful reload. /// - public bool ForceReloadWeapon() + public bool ReloadWeapon() { if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) { @@ -1827,7 +1827,7 @@ public bool ForceReloadWeapon() /// Forces the player to reload their current weapon. /// /// if the firearm was successfully reloaded. Otherwise, . - public bool ReloadWeapon() + public bool TryReloadWeapon() { if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) { @@ -1844,7 +1844,7 @@ public bool ReloadWeapon() /// /// This method does not check if the weapon can actually be unloaded. It only forces the animation and is not guaranteed to result in a successful unload. /// - public bool ForceUnloadWeapon() + public bool UnloadWeapon() { if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) { @@ -1860,7 +1860,7 @@ public bool ForceUnloadWeapon() /// Forces the player to unload their current weapon. ///
/// if the firearm was successfully unloaded. Otherwise, . - public bool UnloadWeapon() + public bool TryUnloadWeapon() { if (CurrentItem is not Firearm firearm || firearm.AnimatorReloaderModule == null) { From 697ca77bf7549b0eac03de3ab366c09e90a423b5 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Tue, 12 Aug 2025 23:40:20 -0400 Subject: [PATCH 120/224] fix: Fall damage not working (#628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I WAS THE CAUSE OF THE FALL DAMAGE BUG NOOOOOOOOOOOO I JUST DIDNT SEE A SINGLE brfalse_s 😭 😭 😭 😭 😭 😭 😭 😭 😭 😭 --- EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs b/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs index 435bcb7eca..6f50676792 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Jumping.cs @@ -47,8 +47,8 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Brfalse_S); + offset = 1; + index = newInstructions.FindIndex(instruction => instruction.StoresField(Field(typeof(FpcMotor), nameof(FpcMotor._maxFallSpeed)))) + offset; // make br_false use stored value newInstructions.Insert(index, new CodeInstruction(OpCodes.Ldloc, jumping)); From 8f4e6528ad0121fb7ea6d9396437aa7695f4a781 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 13 Aug 2025 05:41:33 +0200 Subject: [PATCH 121/224] 9.8.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index fe7f5b2651..83d57709ef 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.8.0 + 9.8.1 false From 92982fe7d894afbde10b192c5169da1d7589bc7c Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:24:06 +0300 Subject: [PATCH 122/224] fix: LeadingTeam incorrect enumeration fix (#625) * no more breaching changes * checking * Update Player.cs Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * Update Player.cs Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * Update LeadingTeam.cs * Update Player.cs --------- Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> --- EXILED/Exiled.API/Enums/LeadingTeam.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/LeadingTeam.cs b/EXILED/Exiled.API/Enums/LeadingTeam.cs index 01d62e6ec2..b136e854f0 100644 --- a/EXILED/Exiled.API/Enums/LeadingTeam.cs +++ b/EXILED/Exiled.API/Enums/LeadingTeam.cs @@ -35,9 +35,14 @@ public enum LeadingTeam : byte ///
Anomalies, + /// + /// Represents the Flamingo team. + /// + Flamingo, + /// /// Represents a draw. /// Draw, } -} \ No newline at end of file +} From 22014d1d9f2a1407bcf5d8f7d558fa6baa4bc617 Mon Sep 17 00:00:00 2001 From: Snivy Films <120346554+SnivyFilms@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:47:37 -0400 Subject: [PATCH 123/224] feat: More Dynamic Spawn Locations (#626) --- EXILED/Exiled.API/Enums/SpawnLocationType.cs | 12 ++++++++++++ EXILED/Exiled.API/Extensions/SpawnExtensions.cs | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Enums/SpawnLocationType.cs b/EXILED/Exiled.API/Enums/SpawnLocationType.cs index ae957d3362..2fc9d2a780 100644 --- a/EXILED/Exiled.API/Enums/SpawnLocationType.cs +++ b/EXILED/Exiled.API/Enums/SpawnLocationType.cs @@ -132,11 +132,13 @@ public enum SpawnLocationType /// /// Inside the lower door that leads to the stairs in Micro-HID room. /// + [Obsolete("This location has been removed from the game.")] InsideHidLower, /// /// Inside the upper door that leads into the Micro-HID room just after the stairs. /// + [Obsolete("This location has been removed from the game. Use InsideHidLab instead.")] InsideHidUpper, /// @@ -168,5 +170,15 @@ public enum SpawnLocationType /// Inside SCP-079's Armory /// Inside079Armory, + + /// + /// Inside SCP-127's Lab + /// + Inside127Lab, + + /// + /// Inside the upper door that leads into the Micro-HID Lab room. + /// + InsideHidLab, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/SpawnExtensions.cs b/EXILED/Exiled.API/Extensions/SpawnExtensions.cs index 29da4ab802..5e21f52eaa 100644 --- a/EXILED/Exiled.API/Extensions/SpawnExtensions.cs +++ b/EXILED/Exiled.API/Extensions/SpawnExtensions.cs @@ -25,7 +25,7 @@ public static class SpawnExtensions { SpawnLocationType.InsideHczArmory, SpawnLocationType.Inside079First, - SpawnLocationType.InsideHidUpper, + SpawnLocationType.InsideHidLab, SpawnLocationType.Inside173Gate, SpawnLocationType.InsideGateA, SpawnLocationType.InsideGateB, @@ -86,11 +86,10 @@ public static Vector3 GetPosition(this SpawnLocationType location) SpawnLocationType.InsideGateA => "GATE_A", SpawnLocationType.InsideGateB => "GATE_B", SpawnLocationType.InsideLczWc => "LCZ_WC", - SpawnLocationType.InsideHidLower => "HID_LOWER", SpawnLocationType.InsideLczCafe => "LCZ_CAFE", SpawnLocationType.Inside173Gate => "173_GATE", SpawnLocationType.InsideIntercom => "INTERCOM", - SpawnLocationType.InsideHidUpper => "HID_UPPER", + SpawnLocationType.InsideHidLab => "HID_LAB", SpawnLocationType.Inside079First => "079_FIRST", SpawnLocationType.Inside330Chamber => "330_CHAMBER", SpawnLocationType.Inside049Armory => "049_ARMORY", @@ -108,6 +107,7 @@ public static Vector3 GetPosition(this SpawnLocationType location) SpawnLocationType.Inside106Secondary => "106_SECONDARY", SpawnLocationType.Inside939Cryo => "939_CRYO", SpawnLocationType.Inside079Armory => "079_ARMORY", + SpawnLocationType.Inside127Lab => "HCZ_127_LAB", _ => default, }; } From 73a81d4c3422509090e8d35728f7527c8a68cce1 Mon Sep 17 00:00:00 2001 From: Yamato Date: Fri, 22 Aug 2025 21:17:35 +0200 Subject: [PATCH 124/224] I am Exceptionnally allowing this breaking change --- EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs | 2 +- EXILED/Exiled.API/Features/Items/Firearm.cs | 2 +- EXILED/Exiled.API/Features/Items/Item.cs | 2 +- EXILED/Exiled.API/Features/Pickups/Pickup.cs | 4 ++-- EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index b95b57b344..38b5132ec7 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -219,7 +219,7 @@ public static bool TryGetSetting(Player player, int id, out T setting) /// New setting won't be synced with players. /// public static T Create(ServerSpecificSettingBase settingBase) - where T : SettingBase => (T)Create(settingBase); + where T : SettingBase => Create(settingBase) as T; /// /// Syncs setting with all players. diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 576ebf78ae..56215591af 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -361,7 +361,7 @@ public RecoilSettings Recoil /// The type of firearm to create. /// The newly created firearm. public static Firearm Create(FirearmType type) - => type is not FirearmType.None ? (Firearm)Create(type.GetItemType()) : null; + => type is not FirearmType.None ? Create(type.GetItemType()) : null; /// /// Adds a to the firearm. diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 839585d0c9..4270d068a6 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -363,7 +363,7 @@ public static T Get(ushort serial) /// The who owns the item by default. /// The specified type. /// The created. This can be cast as a subclass. - public static Item Create(ItemType type, Player owner = null) // TODO modify return type to "T" + public static T Create(ItemType type, Player owner = null) where T : Item => Create(type, owner) as T; /// diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index ab635fa81b..a610217fb3 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -532,7 +532,7 @@ public static IEnumerable Get(IEnumerable gameObjects) /// The specified type. /// The created . /// - public static Pickup Create(ItemType type) // TODO modify return type to "T" + public static T Create(ItemType type) where T : Pickup => Create(type) as T; /// @@ -556,7 +556,7 @@ public static IEnumerable Get(IEnumerable gameObjects) /// The specified type. /// The . See documentation of for more information on casting. /// - public static Pickup CreateAndSpawn(ItemType type, Vector3 position, Quaternion? rotation = null, Player previousOwner = null) + public static T CreateAndSpawn(ItemType type, Vector3 position, Quaternion? rotation = null, Player previousOwner = null) where T : Pickup => CreateAndSpawn(type, position, rotation, previousOwner) as T; /// diff --git a/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs b/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs index 80a129eb67..f9d5b4f10d 100644 --- a/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs +++ b/EXILED/Exiled.API/Features/Pickups/Projectiles/Projectile.cs @@ -111,7 +111,7 @@ internal Projectile(ItemType type) /// The of the projectile. /// The specified type. /// The created . - public static Projectile Create(ProjectileType projectiletype) // TODO modify return type to "T" + public static T Create(ProjectileType projectiletype) where T : Projectile => Create(projectiletype) as T; /// @@ -135,7 +135,7 @@ internal Projectile(ItemType type) /// An optional previous owner of the item. /// The specified type. /// The . See documentation of for more information on casting. - public static Projectile CreateAndSpawn(ProjectileType type, Vector3 position, Quaternion? rotation = null, bool shouldBeActive = true, Player previousOwner = null) + public static T CreateAndSpawn(ProjectileType type, Vector3 position, Quaternion? rotation = null, bool shouldBeActive = true, Player previousOwner = null) where T : Projectile => CreateAndSpawn(type, position, rotation, shouldBeActive, previousOwner) as T; /// From 6696f4b0de48a153a33a341bc5efd55e16da429a Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 23 Aug 2025 11:57:34 +0200 Subject: [PATCH 125/224] Fix NRE error when implicit conversion --- EXILED/Exiled.API/Features/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 960171e036..1238fb2a53 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1205,7 +1205,7 @@ public bool IsSpawnProtected /// /// The LabApi player. /// EXILED player. - public static implicit operator LabApi.Features.Wrappers.Player(Player player) => LabApi.Features.Wrappers.Player.Get(player.ReferenceHub); + public static implicit operator LabApi.Features.Wrappers.Player(Player player) => LabApi.Features.Wrappers.Player.Get(player?.ReferenceHub); /// /// Gets a filtered by side. Can be empty. From cab73adbdbea5447ac203fb8a978b6f3e8951128 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:57:58 +0300 Subject: [PATCH 126/224] fix: CustomRole keep position on spawn (#630) Update CustomRole.cs --- EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 5b226ca2d2..6efb5867c9 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -586,10 +586,14 @@ public virtual void AddRole(Player player) player.Scale = Scale; if (Gravity.HasValue && player.Role is FpcRole fpcRole) fpcRole.Gravity = Gravity.Value; - Vector3 position = GetSpawnPosition(); - if (position != Vector3.zero) + + if (!KeepPositionOnSpawn) { - player.Position = position; + Vector3 position = GetSpawnPosition(); + if (position != Vector3.zero) + { + player.Position = position; + } } Log.Debug($"{Name}: Setting player info"); From 6cd70e754211644db55136c345bd13ba0cf9ad0b Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 3 Sep 2025 22:26:35 +0300 Subject: [PATCH 127/224] fix: Falling into the void at the Pocket Dimension exit (#634) * Create FixCapturePosition.cs * Update FixCapturePosition.cs * Update EXILED/Exiled.Events/Patches/Fixes/FixCapturePosition.cs * Update FixCapturePosition.cs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Patches/Fixes/FixCapturePosition.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixCapturePosition.cs diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixCapturePosition.cs b/EXILED/Exiled.Events/Patches/Fixes/FixCapturePosition.cs new file mode 100644 index 0000000000..cadbc403f0 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/FixCapturePosition.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using CustomPlayerEffects; + using Exiled.API.Enums; + using Exiled.API.Features; + using HarmonyLib; + using RelativePositioning; + using UnityEngine; +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + /// + /// Patches 's setter. + /// Fix for those who go to pocket without effect and to get empty or null capture position and fall into the void. + /// + [HarmonyPatch(typeof(PocketCorroding), nameof(PocketCorroding.CapturePosition), MethodType.Getter)] + internal class FixCapturePosition + { + private const RoomType DefaultRoomType = RoomType.Hcz106; + + private static void Postfix(ref RelativePosition __result) + { + if (Room.Get(__result.Position) != null) + return; + + Room room = Room.Get(DefaultRoomType); + __result = new RelativePosition(room.Position); + } + } +} From 763e0597505e20529e47f9f3a2e5f478456ac68c Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Sun, 7 Sep 2025 15:49:21 +0300 Subject: [PATCH 128/224] feat: version type select (#638) Originally by MikeSus1 ([#520](https://github.com/ExMod-Team/EXILED/pull/520)) --- EXILED/Exiled.Installer/Program.cs | 22 ++ .../Properties/Resources.Designer.cs | 188 ++++++++++++------ .../Properties/Resources.resx | 15 ++ EXILED/Exiled.Loader/Models/NewVersion.cs | 9 +- EXILED/Exiled.Loader/Updater.cs | 7 +- 5 files changed, 175 insertions(+), 66 deletions(-) diff --git a/EXILED/Exiled.Installer/Program.cs b/EXILED/Exiled.Installer/Program.cs index c7a43a2dcd..80132c7070 100644 --- a/EXILED/Exiled.Installer/Program.cs +++ b/EXILED/Exiled.Installer/Program.cs @@ -89,6 +89,28 @@ internal static async Task MainSafe(CommandSettings args) GitHubClient.Credentials = new Credentials(args.GitHubToken, AuthenticationType.Bearer); } + if (string.IsNullOrEmpty(args.TargetVersion) && !args.PreReleases) + { + Console.WriteLine(Resources.Program_MainSafe_Which_version_would_you_like_to_install_); + Console.WriteLine(Resources.Program_MainSafe__1___Latest_stable_release); + Console.WriteLine(Resources.Program_MainSafe__2___Latest_prerelease__beta_dev_); + + Console.Write(Resources.Program_MainSafe_Your_choice__1_2___); + ConsoleKeyInfo key = Console.ReadKey(intercept: true); + Console.WriteLine(key.KeyChar); + + if (key.KeyChar == '2') + { + args.PreReleases = true; + Console.WriteLine(Resources.Program_MainSafe_); + } + else + { + args.PreReleases = false; + Console.WriteLine(Resources.Program_MainSafe_); + } + } + Console.WriteLine(Resources.Program_MainSafe_Receiving_releases___); Console.WriteLine(Resources.Program_MainSafe_Prereleases_included____0_, args.PreReleases); Console.WriteLine(Resources.Program_MainSafe_Target_release_version____0_, string.IsNullOrEmpty(args.TargetVersion) ? "(null)" : args.TargetVersion); diff --git a/EXILED/Exiled.Installer/Properties/Resources.Designer.cs b/EXILED/Exiled.Installer/Properties/Resources.Designer.cs index e382401b53..b95cc93c54 100644 --- a/EXILED/Exiled.Installer/Properties/Resources.Designer.cs +++ b/EXILED/Exiled.Installer/Properties/Resources.Designer.cs @@ -1,39 +1,38 @@ //------------------------------------------------------------------------------ // -// Il codice è stato generato da uno strumento. -// Versione runtime:4.0.30319.42000 +// This code was generated by a tool. // -// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se -// il codice viene rigenerato. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ namespace Exiled.Installer.Properties { using System; - - + + /// - /// Classe di risorse fortemente tipizzata per la ricerca di stringhe localizzate e così via. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Questa classe è stata generata automaticamente dalla classe StronglyTypedResourceBuilder. - // tramite uno strumento quale ResGen o Visual Studio. - // Per aggiungere o rimuovere un membro, modificare il file con estensione ResX ed eseguire nuovamente ResGen - // con l'opzione /str oppure ricompilare il progetto VS. + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - + /// - /// Restituisce l'istanza di ResourceManager nella cache utilizzata da questa classe. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -45,10 +44,10 @@ internal Resources() { return resourceMan; } } - + /// - /// Esegue l'override della proprietà CurrentUICulture del thread corrente per tutte le - /// ricerche di risorse eseguite utilizzando questa classe di risorse fortemente tipizzata. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -59,9 +58,9 @@ internal Resources() { resourceCulture = value; } } - + /// - /// Cerca una stringa localizzata simile a EXILED\:EXILED + /// Looks up a localized string similar to EXILED\:EXILED ///SCP Secret Laboratory\:ABSOLUTE ///. /// @@ -70,108 +69,153 @@ internal static string Markup { return ResourceManager.GetString("Markup", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Does it exist? - {0}. + /// Looks up a localized string similar to Does it exist? - {0}. /// internal static string Program_EnsureDirExists_Does_it_exist_____0_ { get { return ResourceManager.GetString("Program_EnsureDirExists_Does_it_exist_____0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Ensuring directory path: {0}. + /// Looks up a localized string similar to Ensuring directory path: {0}. /// internal static string Program_EnsureDirExists_Ensuring_directory_path___0_ { get { return ResourceManager.GetString("Program_EnsureDirExists_Ensuring_directory_path___0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a An exception occurred while trying to extract a file. + /// Looks up a localized string similar to An exception occurred while trying to extract a file. /// internal static string Program_ExtractEntry_An_exception_occurred_while_trying_to_extract_a_file { get { return ResourceManager.GetString("Program_ExtractEntry_An_exception_occurred_while_trying_to_extract_a_file", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Extracting '{0}' into '{1}'.... + /// Looks up a localized string similar to Extracting '{0}' into '{1}'.... /// internal static string Program_ExtractEntry_Extracting___0___into___1_____ { get { return ResourceManager.GetString("Program_ExtractEntry_Extracting___0___into___1_____", resourceCulture); } } - + + /// + /// Looks up a localized string similar to → Prerelease selected.. + /// + internal static string Program_MainSafe_ { + get { + return ResourceManager.GetString("Program_MainSafe_", resourceCulture); + } + } + /// - /// Cerca una stringa localizzata simile a --- ASSETS ---. + /// Looks up a localized string similar to --- ASSETS ---. /// internal static string Program_MainSafe_____ASSETS____ { get { return ResourceManager.GetString("Program_MainSafe_____ASSETS____", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a --- AVAILABLE VERSIONS ---. + /// Looks up a localized string similar to --- AVAILABLE VERSIONS ---. /// internal static string Program_MainSafe_____AVAILABLE_VERSIONS____ { get { return ResourceManager.GetString("Program_MainSafe_____AVAILABLE_VERSIONS____", resourceCulture); } } - + + /// + /// Looks up a localized string similar to --- RELEASES ---. + /// + internal static string Program_MainSafe_____RELEASES____ { + get { + return ResourceManager.GetString("Program_MainSafe_____RELEASES____", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1 - Latest stable release. + /// + internal static string Program_MainSafe__1___Latest_stable_release { + get { + return ResourceManager.GetString("Program_MainSafe__1___Latest_stable_release", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 2 - Latest prerelease (beta/dev). + /// + internal static string Program_MainSafe__2___Latest_prerelease__beta_dev_ { + get { + return ResourceManager.GetString("Program_MainSafe__2___Latest_prerelease__beta_dev_", resourceCulture); + } + } + /// - /// Cerca una stringa localizzata simile a AppData folder: {0}. + /// Looks up a localized string similar to AppData folder: {0}. /// internal static string Program_MainSafe_AppData_folder___0_ { get { return ResourceManager.GetString("Program_MainSafe_AppData_folder___0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Asset found!. + /// Looks up a localized string similar to Asset found!. /// internal static string Program_MainSafe_Asset_found_ { get { return ResourceManager.GetString("Program_MainSafe_Asset_found_", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Couldn't find '{0}' in '{1}'. + /// + internal static string Program_MainSafe_Couldn_t_find___0___in___1__ { + get { + return ResourceManager.GetString("Program_MainSafe_Couldn_t_find___0___in___1__", resourceCulture); + } + } + /// - /// Cerca una stringa localizzata simile a Exiled folder: {0}. + /// Looks up a localized string similar to Exiled folder: {0}. /// internal static string Program_MainSafe_Exiled_folder___0_ { get { return ResourceManager.GetString("Program_MainSafe_Exiled_folder___0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Installation complete. + /// Looks up a localized string similar to Installation complete. /// internal static string Program_MainSafe_Installation_complete { get { return ResourceManager.GetString("Program_MainSafe_Installation_complete", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Prereleases included - {0}. + /// Looks up a localized string similar to Prereleases included - {0}. /// internal static string Program_MainSafe_Prereleases_included____0_ { get { return ResourceManager.GetString("Program_MainSafe_Prereleases_included____0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Read the exception message, read the readme, and if you still don't understand what to do, then contact #support in our discord server with the attached screenshot of the full exception. + /// Looks up a localized string similar to Read the exception message, read the readme, and if you still don't understand what to do, then contact #support in our discord server with the attached screenshot of the full exception. /// internal static string Program_MainSafe_Read_the_exception_message__read_the_readme__and_if_you_still_don_t_understand_what_to_do__then_contact__support_in_our_discord_server_with_the_attached_screenshot_of_the_full_exception { get { @@ -180,81 +224,99 @@ internal static string Program_MainSafe_Read_the_exception_message__read_the_rea "attached_screenshot_of_the_full_exception", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Receiving releases.... + /// Looks up a localized string similar to Receiving releases.... /// internal static string Program_MainSafe_Receiving_releases___ { get { return ResourceManager.GetString("Program_MainSafe_Receiving_releases___", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Release found!. + /// Looks up a localized string similar to Release found!. /// internal static string Program_MainSafe_Release_found_ { get { return ResourceManager.GetString("Program_MainSafe_Release_found_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Searching for the latest release that matches the parameters.... + /// Looks up a localized string similar to Searching for the latest release that matches the parameters.... /// internal static string Program_MainSafe_Searching_for_the_latest_release_that_matches_the_parameters___ { get { return ResourceManager.GetString("Program_MainSafe_Searching_for_the_latest_release_that_matches_the_parameters___", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Target release version - {0}. + /// Looks up a localized string similar to Target release version - {0}. /// internal static string Program_MainSafe_Target_release_version____0_ { get { return ResourceManager.GetString("Program_MainSafe_Target_release_version____0_", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Token detected! Using the token.... + /// Looks up a localized string similar to Token detected! Using the token.... /// internal static string Program_MainSafe_Token_detected__Using_the_token___ { get { return ResourceManager.GetString("Program_MainSafe_Token_detected__Using_the_token___", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Which version would you like to install?. + /// + internal static string Program_MainSafe_Which_version_would_you_like_to_install_ { + get { + return ResourceManager.GetString("Program_MainSafe_Which_version_would_you_like_to_install_", resourceCulture); + } + } + /// - /// Cerca una stringa localizzata simile a Couldn't resolve path for '{0}', update installer. + /// Looks up a localized string similar to Your choice (1/2): . + /// + internal static string Program_MainSafe_Your_choice__1_2___ { + get { + return ResourceManager.GetString("Program_MainSafe_Your_choice__1_2___", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Couldn't resolve path for '{0}', update installer. /// internal static string Program_ProcessTarEntry_Couldn_t_resolve_path_for___0____update_installer { get { return ResourceManager.GetString("Program_ProcessTarEntry_Couldn_t_resolve_path_for___0____update_installer", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Extract for {0} is disabled. + /// Looks up a localized string similar to Extract for {0} is disabled. /// internal static string Program_ProcessTarEntry_Extract_for__0__is_disabled { get { return ResourceManager.GetString("Program_ProcessTarEntry_Extract_for__0__is_disabled", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Processing '{0}'. + /// Looks up a localized string similar to Processing '{0}'. /// internal static string Program_ProcessTarEntry_Processing___0__ { get { return ResourceManager.GetString("Program_ProcessTarEntry_Processing___0__", resourceCulture); } } - + /// - /// Cerca una stringa localizzata simile a Trying to find release... + /// Looks up a localized string similar to Trying to find release... /// internal static string Program_TryFindRelease_Trying_to_find_release__ { get { diff --git a/EXILED/Exiled.Installer/Properties/Resources.resx b/EXILED/Exiled.Installer/Properties/Resources.resx index 656ed6ad10..f1d2d5c86a 100644 --- a/EXILED/Exiled.Installer/Properties/Resources.resx +++ b/EXILED/Exiled.Installer/Properties/Resources.resx @@ -191,4 +191,19 @@ Exiled folder: {0} + + Which version would you like to install? + + + 1 - Latest stable release + + + 2 - Latest prerelease (beta/dev) + + + Your choice (1/2): + + + → Prerelease selected. + \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Models/NewVersion.cs b/EXILED/Exiled.Loader/Models/NewVersion.cs index 81cb4f3e42..4576ad7292 100644 --- a/EXILED/Exiled.Loader/Models/NewVersion.cs +++ b/EXILED/Exiled.Loader/Models/NewVersion.cs @@ -24,15 +24,22 @@ public readonly struct NewVersion /// public readonly ReleaseAsset Asset; + /// + /// Indicates if the release is a prerelease. + /// + public readonly bool IsPrerelease; + /// /// Initializes a new instance of the struct. /// /// /// - public NewVersion(Release release, ReleaseAsset asset) + /// + public NewVersion(Release release, ReleaseAsset asset, bool isPrerelease) { Release = release; Asset = asset; + IsPrerelease = isPrerelease; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 0c70548bb4..54eaf927ec 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -167,8 +167,11 @@ private bool FindUpdate(HttpClient client, bool forced, out NewVersion newVersio } else { + bool isPrerelease = targetRelease.PreRelease; + Log.Info($"Release type: {(isPrerelease ? "Prerelease" : "Stable")}"); + Log.Info($"Found asset - Name: {asset.Name} | Size: {asset.Size} Download: {asset.BrowserDownloadUrl}"); - newVersion = new NewVersion(targetRelease, asset); + newVersion = new NewVersion(targetRelease, asset, isPrerelease); return true; } } @@ -226,7 +229,7 @@ private void Update(HttpClient client, NewVersion newVersion) FileName = installerPath, UseShellExecute = false, CreateNoWindow = true, - Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{Path.Combine(Paths.Exiled, "..")}\"", + Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{Path.Combine(Paths.Exiled, "..")}\" {(newVersion.IsPrerelease ? "--pre-releases" : string.Empty)}", RedirectStandardOutput = true, RedirectStandardError = true, StandardErrorEncoding = ProcessEncoding, From df52d511368afe387c046c480f79133b73f07b0c Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:06:18 +0200 Subject: [PATCH 129/224] feat: PlaceBlood extensions (#615) PlaceBlood Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> --- EXILED/Exiled.API/Enums/BloodType.cs | 3 + .../Exiled.API/Extensions/MirrorExtensions.cs | 58 +++++++++++++++++++ EXILED/Exiled.API/Features/Map.cs | 3 +- EXILED/Exiled.API/Features/Player.cs | 1 + 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/BloodType.cs b/EXILED/Exiled.API/Enums/BloodType.cs index b054cc0e67..0c77521fd2 100644 --- a/EXILED/Exiled.API/Enums/BloodType.cs +++ b/EXILED/Exiled.API/Enums/BloodType.cs @@ -7,11 +7,14 @@ namespace Exiled.API.Enums { + using System; + /// /// Unique identifier for the different types of blood decals. /// /// /// + [Obsolete("This blood decal are outdated now used DealPoolType.Blood", true)] public enum BloodType { /// diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index a06cd5c294..3a09ab7a3d 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -23,11 +23,13 @@ namespace Exiled.API.Extensions using Features.Pools; using InventorySystem; using InventorySystem.Items; + using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Modules; using MEC; using Mirror; using PlayerRoles; + using PlayerRoles.Blood; using PlayerRoles.FirstPersonControl; using PlayerRoles.PlayableScps.Scp049.Zombies; using PlayerRoles.PlayableScps.Scp1507; @@ -35,6 +37,7 @@ namespace Exiled.API.Extensions using RelativePositioning; using Respawning; using UnityEngine; + using Utils.Networking; /// /// A set of extensions for Networking. @@ -221,6 +224,61 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp }); } + /// + /// Place blood that only the can see. + /// + /// Target to play. + /// The position of the blood decal. + /// The direction of the blood decal. + /// The RoleTypeId from who blood come from. + /// The sound than player get when getting shot. + public static void PlaceBlood(this Player player, Vector3 position, Vector3 origin, RoleTypeId roleTypeId, int gettingShotSoundIndex) + { + if (!roleTypeId.TryGetRoleBase(out PlayerRoleBase playerRoleBase) || playerRoleBase is not IBleedableRole) + return; + + Features.Items.Firearm firearm = Features.Items.Firearm.ItemTypeToFirearmInstance[FirearmType.Com15]; + + if (firearm == null) + return; + + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + { + writer.WriteUShort(NetworkMessageId.Id); + new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub).Write(writer); + writer.WriteRelativePosition(new RelativePosition(0, 0, 0, 0, false)); + writer.WriteUShort(0); + player.Connection.Send(writer); + } + + player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), firearm.Identifier); + + if (!firearm.Base.TryGetModule(out ImpactEffectsModule impactEffectsModule)) + return; + + Timing.CallDelayed(0.1f, () => // due to selecting item we need to delay shot a bit + { + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + { +#pragma warning disable SA1116 // Split parameters should start on line after declaration + impactEffectsModule.SendRpc(writer => + { + writer.WriteSubheader(ImpactEffectsModule.RpcType.PlayerHit); + writer.WriteReferenceHub(Server.Host.ReferenceHub); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(origin)); + writer.WriteByte(255); + writer.WriteRoleType(RoleTypeId.ClassD); + }, true); +#pragma warning restore SA1116 // Split parameters should start on line after declaration + } + + player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), ItemIdentifier.None); + + player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub)); + }); + } + /// /// Sets that only the player can see. /// diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 90ca019f81..7e7208cbc6 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -327,7 +327,8 @@ public static void CleanAllRagdolls(IEnumerable ragDolls) /// /// The position of the blood decal. /// The direction of the blood decal. - public static void PlaceBlood(Vector3 position, Vector3 direction) => _ = 0; /* new GunDecalMessage(position, direction, DecalPoolType.Blood).SendToAuthenticated(0);*/ // TODO: Not finish + [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] + public static void PlaceBlood(Vector3 position, Vector3 direction) => _ = 0; /// /// Gets all the near cameras. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 1238fb2a53..d81ea63f72 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3629,6 +3629,7 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = this.PlayGunSound(Position, itemType, pitch, clipIndex); /// + [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); /// From 05a4ef69bffc948f167627b053bf7b2b3efcf72d Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Sun, 7 Sep 2025 17:43:44 +0300 Subject: [PATCH 130/224] fix: MicroHIDOpeningDoor event fix (#639) --- .../EventArgs/Player/MicroHIDOpeningDoorEventArgs.cs | 2 +- .../Patches/Events/Player/MicroHIDOpeningDoor.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/MicroHIDOpeningDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/MicroHIDOpeningDoorEventArgs.cs index 36ff69e9b1..fc1024823f 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/MicroHIDOpeningDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/MicroHIDOpeningDoorEventArgs.cs @@ -29,7 +29,7 @@ public MicroHIDOpeningDoorEventArgs(MicroHIDItem item, DoorVariant door, bool is { MicroHID = Item.Get(item); Player = MicroHID.Owner; - IsAllowed = isAllowed; + IsAllowed = !isAllowed; Door = Door.Get(door); } diff --git a/EXILED/Exiled.Events/Patches/Events/Player/MicroHIDOpeningDoor.cs b/EXILED/Exiled.Events/Patches/Events/Player/MicroHIDOpeningDoor.cs index 262114b6ad..a631bdd812 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/MicroHIDOpeningDoor.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/MicroHIDOpeningDoor.cs @@ -60,8 +60,10 @@ private static IEnumerable Transpiler(IEnumerable Date: Sun, 7 Sep 2025 23:07:43 +0300 Subject: [PATCH 131/224] fix: Hurt and Hurting Attacker property fix (#640) fix: Attacker being null if GenericDamageHandler is used --- EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs | 8 +++++++- EXILED/Exiled.Events/EventArgs/Player/HurtingEventArgs.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs index 8fd0eab99b..b42a7ab209 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/HurtEventArgs.cs @@ -36,8 +36,14 @@ public HurtEventArgs(ReferenceHub referenceHub, DamageHandlerBase damageHandler, { Player = Player.Get(referenceHub); DamageHandler = new CustomDamageHandler(Player, damageHandler); - Attacker = DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler) ? attackerDamageHandler.Attacker : null; HandlerOutput = handlerOutput; + + if (DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler)) + Attacker = attackerDamageHandler.Attacker; + else if (damageHandler is GenericDamageHandler genericDamageHandler) + Attacker = Player.Get(genericDamageHandler.Attacker); + else + Attacker = null; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/HurtingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/HurtingEventArgs.cs index ce3507b18a..f328bddd20 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/HurtingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/HurtingEventArgs.cs @@ -33,8 +33,14 @@ public HurtingEventArgs(Player target, DamageHandlerBase damageHandler) { DamageHandler = new CustomDamageHandler(target, damageHandler); - Attacker = DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler) ? attackerDamageHandler.Attacker : null; Player = target; + + if (DamageHandler.BaseIs(out CustomAttackerHandler attackerDamageHandler)) + Attacker = attackerDamageHandler.Attacker; + else if (damageHandler is GenericDamageHandler genericDamageHandler) + Attacker = Player.Get(genericDamageHandler.Attacker); + else + Attacker = null; } /// From 13705bf4a6316f07f6ba205612db479f65dd8a01 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:49:52 +0300 Subject: [PATCH 132/224] fix: Custom Weapons Ammo Limit / Reloading & Unloading Events (#633) * Update CustomWeapon.cs * Update ReloadingWeapon.cs * Update ReloadingWeapon.cs * Update Player.cs * Update Events.cs * Update Player.cs * Update ReloadingWeaponEventArgs.cs * Update UnloadingWeaponEventArgs.cs * Update Player.cs * Delete EXILED/Exiled.Events/Patches/Events/Player/ReloadingWeapon.cs --- .../API/Features/CustomWeapon.cs | 8 +- .../Player/ReloadingWeaponEventArgs.cs | 10 +- .../Player/UnloadingWeaponEventArgs.cs | 10 +- EXILED/Exiled.Events/Events.cs | 6 + EXILED/Exiled.Events/Handlers/Player.cs | 15 ++- .../Patches/Events/Player/ReloadingWeapon.cs | 103 ------------------ 6 files changed, 35 insertions(+), 117 deletions(-) delete mode 100644 EXILED/Exiled.Events/Patches/Events/Player/ReloadingWeapon.cs diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs index 733e6317d2..9053cf2d66 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs @@ -221,7 +221,7 @@ protected virtual void OnChangingAttachment(ChangingAttachmentsEventArgs ev) private void OnInternalReloading(ReloadingWeaponEventArgs ev) { - if (!Check(ev.Player.CurrentItem)) + if (!Check(ev.Item)) return; if (ClipSize > 0 && ev.Firearm.TotalAmmo >= ClipSize) @@ -266,7 +266,7 @@ private void OnInternalReloaded(ReloadedWeaponEventArgs ev) private void OnInternalShooting(ShootingEventArgs ev) { - if (!Check(ev.Player.CurrentItem)) + if (!Check(ev.Item)) return; OnShooting(ev); @@ -274,7 +274,7 @@ private void OnInternalShooting(ShootingEventArgs ev) private void OnInternalShot(ShotEventArgs ev) { - if (!Check(ev.Player.CurrentItem)) + if (!Check(ev.Item)) return; OnShot(ev); @@ -340,4 +340,4 @@ private void OnInternalChangingAttachment(ChangingAttachmentsEventArgs ev) OnChangingAttachment(ev); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReloadingWeaponEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReloadingWeaponEventArgs.cs index e35fc9f412..a33a3cabbc 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ReloadingWeaponEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ReloadingWeaponEventArgs.cs @@ -20,13 +20,13 @@ public class ReloadingWeaponEventArgs : IPlayerEvent, IFirearmEvent, IDeniableEv /// /// Initializes a new instance of the class. /// - /// - /// - /// - public ReloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm) + /// The firearm being reloaded. + /// A value indicating whether the weapon can be reloaded. + public ReloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm, bool isAllowed) { Firearm = Item.Get(firearm); Player = Firearm.Owner; + IsAllowed = isAllowed; } /// @@ -47,4 +47,4 @@ public ReloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm) /// public Player Player { get; } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/UnloadingWeaponEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/UnloadingWeaponEventArgs.cs index 68fc0df47c..2ac5ce4a2f 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/UnloadingWeaponEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/UnloadingWeaponEventArgs.cs @@ -21,12 +21,16 @@ public class UnloadingWeaponEventArgs : IPlayerEvent, IFirearmEvent, IDeniableEv /// Initializes a new instance of the class. /// /// - /// + /// The firearm being unloaded. /// - public UnloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm) + /// + /// Indicates whether the weapon unloading is allowed. + /// + public UnloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm, bool isAllowed) { Firearm = Item.Get(firearm); Player = Firearm.Owner; + IsAllowed = isAllowed; } /// @@ -47,4 +51,4 @@ public UnloadingWeaponEventArgs(InventorySystem.Items.Firearms.Firearm firearm) /// public Player Player { get; } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index b2c8e47e19..1645fe20a1 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -89,6 +89,9 @@ public override void OnEnabled() ServerSpecificSettingsSync.ServerOnSettingValueReceived += SettingBase.OnSettingUpdated; + LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon += Handlers.Player.OnReloadingWeapon; + LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon += Handlers.Player.OnUnloadingWeapon; + ServerConsole.ReloadServerName(); } @@ -123,6 +126,9 @@ public override void OnDisabled() ItemPickupBase.OnPickupDestroyed -= Handlers.Internal.PickupEvent.OnRemovedPickup; ServerSpecificSettingsSync.ServerOnSettingValueReceived -= SettingBase.OnSettingUpdated; + + LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon -= Handlers.Player.OnReloadingWeapon; + LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon -= Handlers.Player.OnUnloadingWeapon; } /// diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index 590e4924d1..90c721dfd8 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -16,6 +16,7 @@ namespace Exiled.Events.Handlers using Exiled.Events.EventArgs.Player; using Exiled.Events.Features; + using LabApi.Events.Arguments.PlayerEvents; /// /// Player related events. @@ -884,7 +885,12 @@ public class Player /// Called before a reloads a weapon. /// /// The instance. - public static void OnReloadingWeapon(ReloadingWeaponEventArgs ev) => ReloadingWeapon.InvokeSafely(ev); + public static void OnReloadingWeapon(PlayerReloadingWeaponEventArgs ev) + { + ReloadingWeaponEventArgs exiledEv = new(ev.FirearmItem.Base, ev.IsAllowed); + ReloadingWeapon.InvokeSafely(exiledEv); + ev.IsAllowed = exiledEv.IsAllowed; + } /// /// Called after a reloads a weapon. @@ -992,7 +998,12 @@ public class Player /// Called before a unloads a weapon. /// /// The instance. - public static void OnUnloadingWeapon(UnloadingWeaponEventArgs ev) => UnloadingWeapon.InvokeSafely(ev); + public static void OnUnloadingWeapon(PlayerUnloadingWeaponEventArgs ev) + { + UnloadingWeaponEventArgs exiledEv = new(ev.FirearmItem.Base, ev.IsAllowed); + UnloadingWeapon.InvokeSafely(exiledEv); + ev.IsAllowed = exiledEv.IsAllowed; + } /// /// Called after a unloads a weapon. diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ReloadingWeapon.cs b/EXILED/Exiled.Events/Patches/Events/Player/ReloadingWeapon.cs deleted file mode 100644 index 347e82bad1..0000000000 --- a/EXILED/Exiled.Events/Patches/Events/Player/ReloadingWeapon.cs +++ /dev/null @@ -1,103 +0,0 @@ -// ----------------------------------------------------------------------- -// -// 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 Exiled.API.Extensions; - using Exiled.Events.Attributes; - using Exiled.Events.EventArgs.Player; - using HarmonyLib; - using InventorySystem.Items.Firearms.Modules; - - using static HarmonyLib.AccessTools; - - /// - /// Patches . - /// Adds the and event. - /// - [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ReloadingWeapon))] - [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.UnloadingWeapon))] - [HarmonyPatch(typeof(AnimatorReloaderModuleBase), nameof(AnimatorReloaderModuleBase.ServerProcessCmd))] - internal static class ReloadingWeapon - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - int offset = -2; - int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(IReloadUnloadValidatorModule), nameof(IReloadUnloadValidatorModule.ValidateReload)))) + offset; - - Label stopMessage = generator.DefineLabel(); - - newInstructions.InsertRange( - index, - new[] - { - // this.Firearm - new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), - new(OpCodes.Callvirt, PropertyGetter(typeof(AnimatorReloaderModuleBase), nameof(AnimatorReloaderModuleBase.Firearm))), - - // ReloadingWeaponEventArgs ev = new(firearm) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ReloadingWeaponEventArgs))[0]), - new(OpCodes.Dup), - - // Player.OnReloadingWeapon(ev) - new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnReloadingWeapon))), - - // if (!ev.IsAllowed) - // this.SendRpc(NetworkWriter x => - // { - // x.WriteSubheader(AnimatorReloaderModuleBase.ReloaderMessageHeader.Stop); - // }, true); - new(OpCodes.Callvirt, PropertyGetter(typeof(ReloadingWeaponEventArgs), nameof(ReloadingWeaponEventArgs.IsAllowed))), - new(OpCodes.Brfalse, stopMessage), - }); - - offset = -2; - index = newInstructions.FindIndex(x => x.Calls(Method(typeof(IReloadUnloadValidatorModule), nameof(IReloadUnloadValidatorModule.ValidateUnload)))) + offset; - - newInstructions.InsertRange( - index, - new[] - { - // this.Firearm - new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), - new(OpCodes.Callvirt, PropertyGetter(typeof(AnimatorReloaderModuleBase), nameof(AnimatorReloaderModuleBase.Firearm))), - - // UnloadingWeaponEventArgs ev = new(firearm) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(UnloadingWeaponEventArgs))[0]), - new(OpCodes.Dup), - - // Player.OnUnloadingWeapon(ev) - new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnUnloadingWeapon))), - - // if (!ev.IsAllowed) - // this.SendRpc(NetworkWriter x => - // { - // x.WriteSubheader(AnimatorReloaderModuleBase.ReloaderMessageHeader.Stop); - // }, true); - new (OpCodes.Callvirt, PropertyGetter(typeof(UnloadingWeaponEventArgs), nameof(UnloadingWeaponEventArgs.IsAllowed))), - new(OpCodes.Brfalse, stopMessage), - }); - - offset = 2; - index = newInstructions.FindIndex(x => x.operand == (object)PropertyGetter(typeof(AnimatorReloaderModuleBase), nameof(AnimatorReloaderModuleBase.IsUnloading))) + offset; - - newInstructions[index].labels.Add(stopMessage); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} From a3e92156c4851107412ba27a18e2a2cc812c858e Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:54:08 +0300 Subject: [PATCH 133/224] fix: Quick Fix (#641) --- EXILED/Exiled.Example/Events/PlayerHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.Example/Events/PlayerHandler.cs b/EXILED/Exiled.Example/Events/PlayerHandler.cs index 6af4398923..37701f6555 100644 --- a/EXILED/Exiled.Example/Events/PlayerHandler.cs +++ b/EXILED/Exiled.Example/Events/PlayerHandler.cs @@ -19,6 +19,7 @@ namespace Exiled.Example.Events using Exiled.Events.EventArgs.Player; using Exiled.Events.EventArgs.Scp106; using Exiled.Events.EventArgs.Scp914; + using LabApi.Events.Arguments.PlayerEvents; using MEC; @@ -162,7 +163,7 @@ public void OnShooting(ShootingEventArgs ev) Log.Info($"{ev.Player.Nickname} is shooting a {ev.Player.CurrentItem.Type}! Target Pos: {ev.ClaimedTarget?.Position} Direction: {ev.Direction} Allowed: {ev.IsAllowed}"); } - /// + /// public void OnReloading(ReloadingWeaponEventArgs ev) { Log.Info($"{ev.Player.Nickname} is reloading their {ev.Firearm.Type}. They have {ev.Firearm.MagazineAmmo} ammo. Using ammo type {ev.Firearm.AmmoType}"); From 91a668270a0988c9942db0dc8a477648a8f30cb8 Mon Sep 17 00:00:00 2001 From: Valentin Arthur Thomas <64769541+warquys@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:04:17 +0200 Subject: [PATCH 134/224] feat: Implement IPickupEvent to ExplodingGrenadeEventArgs and ChangedIntoGrenadeEventArgs (#433) * Implement IPickupEvent to ExplodingGrenadeEventArgs and ChangedIntoGrenadeEventArgs * no need for warning supressor * no need for warning supressor --------- Co-authored-by: Yamato --- .../EventArgs/Map/ChangedIntoGrenadeEventArgs.cs | 5 ++++- .../Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Map/ChangedIntoGrenadeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/ChangedIntoGrenadeEventArgs.cs index 94f5b93a3e..197c741949 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/ChangedIntoGrenadeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/ChangedIntoGrenadeEventArgs.cs @@ -15,7 +15,7 @@ namespace Exiled.Events.EventArgs.Map /// /// Contains all information for when the server is turned a pickup into a live grenade. /// - public class ChangedIntoGrenadeEventArgs : IExiledEvent + public class ChangedIntoGrenadeEventArgs : IExiledEvent, IPickupEvent { /// /// Initializes a new instance of the class. @@ -37,5 +37,8 @@ public ChangedIntoGrenadeEventArgs(TimedGrenadePickup pickup, ThrownProjectile p /// Gets a value indicating the projectile that spawned. /// public Projectile Projectile { get; } + + /// + Pickup IPickupEvent.Pickup => Pickup; } } diff --git a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs index 7cb2613ced..a0bba7e674 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs @@ -25,7 +25,7 @@ namespace Exiled.Events.EventArgs.Map /// /// Contains all information before a grenade explodes. /// - public class ExplodingGrenadeEventArgs : IPlayerEvent, IDeniableEvent + public class ExplodingGrenadeEventArgs : IPlayerEvent, IDeniableEvent, IPickupEvent { private ExplosionType explosionType; @@ -148,5 +148,8 @@ public ExplosionType ExplosionType /// Gets the player who thrown the grenade. /// public Player Player { get; } + + /// + Pickup IPickupEvent.Pickup => Projectile; } } \ No newline at end of file From a30b5f6d0cf9da71ed344f7071ba55731bf3fffd Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:39:52 -0400 Subject: [PATCH 135/224] feat: Add CustomKeycard wrapper n stuff (#632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Some stuff * RELEASE MEEEEEEEEEEEEEEE WHY MUST I SUFFERRRRRR * Fix stuff * Add Create methods * I am Exceptionnally allowing this breaking change * do not look this * Refactor keycard item creation logic Moved custom keycard instantiation logic from CustomKeycard.cs to Item.cs, simplifying the process and removing redundant internal methods. Keycard creation now uses direct type checks and switch expressions for improved clarity and maintainability. * Fix using error * Many headaches WE LOVEE NWWW I LOVE NON-INSTANCED GFX YIPEEEEEEEEE (things might be still borked btw) * Fix color and CustomKeycards using old values, * Modification on KeycardCustom being Inherited by Keycard * Fix build error * Using Color32 and use of void Prefix * Oups HarmonyPrefix * CustomSerialNumberDetail * Fix NW bug lol * Consolidate dictionaries + add clearing * Poor choice of method (and using error) * add ToString override * I fix * Rename stuff, revert to Color, add FindMatch and CustomKeycardPickup stuff * Add stability basically all done I think 😭 cant wait for NW to change custom keycards to sync vars next update and make all my work obsolete * Use Color.clear instead of new Color32 * just improving code readability and fix matchDesign * Add ChaosKeycard and SingleUseKeycard Introduced new ChaosKeycard and SingleUseKeycard types for keycard items, updating Item.cs logic to instantiate these specific classes. Updated Keycard.cs to support owner parameter and improved type handling. Added reference to Snake.dll for ChaosKeycard functionality. Clarified documentation in CustomKeycardItem.cs. * PartiallyRevert previous commit * Add the Pickup Class for SingleUse And Chaos Keycard * minor docs change * Add SingleUseKeycard pickup info reading and ChaosKeycard SnakeEngine property Implemented ReadPickupInfoBefore in SingleUseKeycard to populate properties from its pickup. Added a SnakeEngine property to ChaosKeycardPickup for accessing the underlying engine instance. * Fix Keycard::Create using wrong ItemType * tinybrain moment :trollface: * Add Uses property for SingleUseKeycard and implement functionality * Fix a bit of silliness Just slightly more optomized * slight forgor? * Fix Rank + Accidental Resync call * PredefinedPerms being editable (not sync) * Remove blank lines between get and set methods * No need of multiple Log combine them all --------- Co-authored-by: Yamato Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Exiled.API.csproj | 2 + .../Exiled.API/Extensions/ItemExtensions.cs | 8 + .../Exiled.API/Extensions/MirrorExtensions.cs | 27 ++ EXILED/Exiled.API/Features/Items/Item.cs | 29 +- EXILED/Exiled.API/Features/Items/Keycard.cs | 34 ++- .../Features/Items/Keycards/ChaosKeycard.cs | 54 ++++ .../Items/Keycards/CustomKeycardItem.cs | 256 ++++++++++++++++++ .../Features/Items/Keycards/KeycardData.cs | 62 +++++ .../Items/Keycards/ManagementKeycard.cs | 88 ++++++ .../Features/Items/Keycards/MetalKeycard.cs | 128 +++++++++ .../Items/Keycards/PermissionsProvider.cs | 73 +++++ .../Items/Keycards/SingleUseKeycard.cs | 126 +++++++++ .../Features/Items/Keycards/Site02Keycard.cs | 114 ++++++++ .../Items/Keycards/TaskForceKeycard.cs | 101 +++++++ EXILED/Exiled.API/Features/Map.cs | 5 + .../Features/Pickups/KeycardPickup.cs | 11 +- .../Pickups/Keycards/ChaosKeycardPickup.cs | 48 ++++ .../Pickups/Keycards/CustomKeycardPickup.cs | 230 ++++++++++++++++ .../Keycards/ManagementKeycardPickup.cs | 84 ++++++ .../Pickups/Keycards/MetalKeycardPickup.cs | 126 +++++++++ .../Keycards/SingleUseKeycardPickup.cs | 87 ++++++ .../Pickups/Keycards/Site02KeycardPickup.cs | 112 ++++++++ .../Keycards/TaskForceKeycardPickup.cs | 99 +++++++ EXILED/Exiled.API/Features/Pickups/Pickup.cs | 23 +- .../Interfaces/Keycards/ILabelKeycard.cs | 28 ++ .../Interfaces/Keycards/INameTagKeycard.cs | 22 ++ .../Interfaces/Keycards/IRankKeycard.cs | 23 ++ .../Keycards/ISerialNumberKeycard.cs | 23 ++ .../Interfaces/Keycards/IWearKeycard.cs | 22 ++ .../Patches/Fixes/NameTagDetailFix.cs | 57 ++++ .../Patches/Fixes/ThrownCustomKeycardFix.cs | 100 +++++++ .../CustomItemNameDetailData.cs | 38 +++ .../KeycardDetails/CustomLabelDetailData.cs | 40 +++ .../KeycardDetails/CustomPermsDetailData.cs | 28 ++ .../KeycardDetails/CustomRankDetailData.cs | 41 +++ .../CustomSerialNumberDetailData.cs | 38 +++ .../KeycardDetails/CustomTintDetailData.cs | 38 +++ .../KeycardDetails/CustomWearDetailData.cs | 38 +++ .../KeycardDetails/NameTagDetailData.cs | 37 +++ .../Generic/SingleUseKeycardRemainingUses.cs | 81 ++++++ 40 files changed, 2560 insertions(+), 21 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/CustomKeycardItem.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/KeycardData.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/ManagementKeycard.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/PermissionsProvider.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/SingleUseKeycard.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs create mode 100644 EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/ChaosKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/MetalKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/SingleUseKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/Site02KeycardPickup.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Keycards/TaskForceKeycardPickup.cs create mode 100644 EXILED/Exiled.API/Interfaces/Keycards/ILabelKeycard.cs create mode 100644 EXILED/Exiled.API/Interfaces/Keycards/INameTagKeycard.cs create mode 100644 EXILED/Exiled.API/Interfaces/Keycards/IRankKeycard.cs create mode 100644 EXILED/Exiled.API/Interfaces/Keycards/ISerialNumberKeycard.cs create mode 100644 EXILED/Exiled.API/Interfaces/Keycards/IWearKeycard.cs create mode 100644 EXILED/Exiled.Events/Patches/Fixes/NameTagDetailFix.cs create mode 100644 EXILED/Exiled.Events/Patches/Fixes/ThrownCustomKeycardFix.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomItemNameDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomLabelDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomPermsDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomRankDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomSerialNumberDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomTintDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomWearDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/KeycardDetails/NameTagDetailData.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/SingleUseKeycardRemainingUses.cs diff --git a/EXILED/Exiled.API/Exiled.API.csproj b/EXILED/Exiled.API/Exiled.API.csproj index c8737a790c..84dd05ff53 100644 --- a/EXILED/Exiled.API/Exiled.API.csproj +++ b/EXILED/Exiled.API/Exiled.API.csproj @@ -31,12 +31,14 @@ + + diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs index 1c03c97d9c..348f7e635f 100644 --- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs +++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs @@ -84,6 +84,14 @@ public static class ItemExtensions /// Returns whether the is a keycard. public static bool IsKeycard(this ItemType type) => GetCategory(type) == ItemCategory.Keycard; + /// + /// Checks if an is a custom keycard. + /// + /// The item to be checked. + /// Returns where the is a custom keycard. + /// This just signifies whether the ItemType represents a keycard that has modifiable properties. + public static bool IsCustomKeycard(this ItemType type) => type is ItemType.KeycardCustomTaskForce or ItemType.KeycardCustomSite02 or ItemType.KeycardCustomManagement or ItemType.KeycardCustomMetalCase; + /// /// Given an , returns the matching . /// diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 3a09ab7a3d..72f4c05a1b 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -19,6 +19,8 @@ namespace Exiled.API.Extensions using CustomPlayerEffects; using Exiled.API.Enums; using Exiled.API.Features.Items; + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Features.Pickups.Keycards; using Features; using Features.Pools; using InventorySystem; @@ -26,6 +28,7 @@ namespace Exiled.API.Extensions using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Keycards; using MEC; using Mirror; using PlayerRoles; @@ -459,6 +462,30 @@ public static void SendFakeEffectTo(this Player effectOwner, Player target, Effe }); } + /// + /// Makes the server resend a message to all clients updating a keycards details to current values. + /// + /// The keycard to resync. + public static void ResyncKeycardItem(CustomKeycardItem customKeycardItem) + { + if (KeycardDetailSynchronizer.Database.Remove(customKeycardItem.Serial)) + { + KeycardDetailSynchronizer.ServerProcessItem(customKeycardItem.Base); + } + } + + /// + /// Makes the server resend a message to all clients updating a keycards details to current values. + /// + /// The keycard to resync. + public static void ResyncKeycardPickup(CustomKeycardPickup customKeycard) + { + if (KeycardDetailSynchronizer.Database.Remove(customKeycard.Serial)) + { + KeycardDetailSynchronizer.ServerProcessPickup(customKeycard.Base); + } + } + /// /// Send CASSIE announcement that only can hear. /// diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 4270d068a6..01c71eec71 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -12,6 +12,7 @@ namespace Exiled.API.Features.Items using Exiled.API.Extensions; using Exiled.API.Features.Core; + using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; using InventorySystem; @@ -217,7 +218,19 @@ public static Item Get(ItemBase itemBase) return itemBase switch { InventorySystem.Items.Firearms.Firearm firearm => new Firearm(firearm), - KeycardItem keycard => new Keycard(keycard), + KeycardItem keycard => keycard switch + { + ChaosKeycardItem chaosKeycardItem => new ChaosKeycard(chaosKeycardItem), + SingleUseKeycardItem singleUseKeycardItem => new SingleUseKeycard(singleUseKeycardItem), + _ => keycard.ItemTypeId switch + { + ItemType.KeycardCustomTaskForce => new TaskForceKeycard(keycard), + ItemType.KeycardCustomSite02 => new Site02Keycard(keycard), + ItemType.KeycardCustomManagement => new ManagementKeycard(keycard), + ItemType.KeycardCustomMetalCase => new MetalKeycard(keycard), + _ => new Keycard(keycard), + } + }, UsableItem usable => usable switch { Scp330Bag scp330Bag => new Scp330(scp330Bag), @@ -304,7 +317,19 @@ public static T Get(ushort serial) public static Item Create(ItemType type, Player owner = null) => type.GetTemplate() switch { InventorySystem.Items.Firearms.Firearm => new Firearm(type), - KeycardItem => new Keycard(type), + KeycardItem keycard => keycard switch + { + ChaosKeycardItem => new ChaosKeycard(type), + SingleUseKeycardItem => new SingleUseKeycard(type), + _ => type switch + { + ItemType.KeycardCustomTaskForce => new TaskForceKeycard(type), + ItemType.KeycardCustomSite02 => new Site02Keycard(type), + ItemType.KeycardCustomManagement => new ManagementKeycard(type), + ItemType.KeycardCustomMetalCase => new MetalKeycard(type), + _ => new Keycard(type, owner), + } + }, UsableItem usable => usable switch { Scp330Bag => new Scp330(), diff --git a/EXILED/Exiled.API/Features/Items/Keycard.cs b/EXILED/Exiled.API/Features/Items/Keycard.cs index 4e68dd60a5..e153ebdbda 100644 --- a/EXILED/Exiled.API/Features/Items/Keycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycard.cs @@ -7,10 +7,9 @@ namespace Exiled.API.Features.Items { - using System; - using Exiled.API.Enums; using Exiled.API.Interfaces; + using Interactables.Interobjects.DoorUtils; using InventorySystem.Items.Keycards; /// @@ -32,8 +31,9 @@ public Keycard(KeycardItem itemBase) /// Initializes a new instance of the class. /// /// The of the keycard. - internal Keycard(ItemType type) - : this((KeycardItem)Server.Host.Inventory.CreateItemInstance(new(type, 0), false)) + /// The owner of the grenade. Leave for no owner. + internal Keycard(ItemType type, Player owner = null) + : this((KeycardItem)(owner ?? Server.Host).Inventory.CreateItemInstance(new(type, 0), false)) { } @@ -45,26 +45,32 @@ internal Keycard(ItemType type) /// /// Gets or sets the of the keycard. /// - public KeycardPermissions Permissions + public virtual KeycardPermissions Permissions { get { foreach (DetailBase detail in Base.Details) { - switch (detail) - { - case PredefinedPermsDetail predefinedPermsDetail: - return (KeycardPermissions)predefinedPermsDetail.Levels.Permissions; - case CustomPermsDetail customPermsDetail: - return (KeycardPermissions)customPermsDetail.GetPermissions(null); - } + if (detail is IDoorPermissionProvider doorPermissionProvider) + return (KeycardPermissions)doorPermissionProvider.GetPermissions(null); } return KeycardPermissions.None; } - [Obsolete("Not functional anymore", true)] - set => _ = value; + set + { + foreach (DetailBase detail in Base.Details) + { + if (detail is PredefinedPermsDetail doorPermissionProvider) + { + KeycardLevels keycardLevels = new((DoorPermissionFlags)value); + doorPermissionProvider._containmentLevel = keycardLevels.Containment; + doorPermissionProvider._armoryLevel = keycardLevels.Armory; + doorPermissionProvider._adminLevel = keycardLevels.Admin; + } + } + } } /// diff --git a/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs new file mode 100644 index 0000000000..8d8bdad39d --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Keycards.Snake; + + /// + /// A base class for chaos keycard items. + /// + public class ChaosKeycard : Keycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal ChaosKeycard(ChaosKeycardItem itemBase) + : base(itemBase) + { + Base = itemBase; + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal ChaosKeycard(ItemType type) + : this((ChaosKeycardItem)Server.Host.Inventory.CreateItemInstance(new(type, 0), false)) + { + } + + /// + /// Gets the this encapsulates. + /// + public new ChaosKeycardItem Base { get; } + + /// + /// Gets the this encapsulates. + /// + public SnakeEngine SnakeEngine => Base._localEngine; + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} == ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/CustomKeycardItem.cs b/EXILED/Exiled.API/Features/Items/Keycards/CustomKeycardItem.cs new file mode 100644 index 0000000000..7497cfef56 --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/CustomKeycardItem.cs @@ -0,0 +1,256 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using System; + using System.Collections.Generic; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features.Pools; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + using UnityEngine; + + /// + /// A base class for all custom keycard items. + /// + public abstract class CustomKeycardItem : Keycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal CustomKeycardItem(KeycardItem itemBase) + : base(itemBase) + { + Base = itemBase; + + if (!DataDict.ContainsKey(Serial)) + DataDict[Serial] = new KeycardData(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal CustomKeycardItem(ItemType type) + : this((KeycardItem)Server.Host.Inventory.CreateItemInstance(new(type, 0), false)) + { + } + + /// + /// Gets the this encapsulates. + /// + public new KeycardItem Base { get; } + + /// + /// Gets or sets the permissions this keycard has. + /// + public override KeycardPermissions Permissions + { + get => CustomPermsDetail.CustomPermissions.TryGetValue(Serial, out DoorPermissionFlags flags) ? (KeycardPermissions)flags : KeycardPermissions.None; + set + { + CustomPermsDetail.CustomPermissions[Serial] = (DoorPermissionFlags)value; + + Resync(); + } + } + + /// + /// Gets or sets the permissions this keycard has. + /// + public KeycardLevels KeycardLevels + { + get => new((DoorPermissionFlags)Permissions); + set + { + CustomPermsDetail.CustomPermissions[Serial] = value.Permissions; + + Resync(); + } + } + + /// + /// Gets or sets the color of this keycard's permissions. + /// + public Color PermissionsColor + { + get => DataDict[Serial].PermissionsColor ?? Color.clear; + set + { + DataDict[Serial].PermissionsColor = value; + + Resync(); + } + } + + /// + /// Gets or sets the name of this keycard within the inventory. + /// + public string ItemName + { + get => DataDict[Serial].ItemName; + set + { + DataDict[Serial].ItemName = value; + + Resync(); + } + } + + /// + /// Gets or sets the color of this keycard. + /// + public Color Color + { + get => DataDict[Serial].Color ?? Color.clear; + set + { + DataDict[Serial].Color = value; + + Resync(); + } + } + + /// + /// Gets a dictionary of item serials to their keycards data. + /// + internal static Dictionary DataDict { get; } = new(); + + /// + /// Gets an array of ItemTypes of each keycard. + /// + internal static ItemType[] AllKeycards { get; } = + { + ItemType.KeycardJanitor, ItemType.KeycardScientist, ItemType.KeycardResearchCoordinator, ItemType.KeycardContainmentEngineer, ItemType.KeycardZoneManager, ItemType.KeycardFacilityManager, ItemType.KeycardGuard, ItemType.KeycardMTFPrivate, ItemType.KeycardMTFOperative, ItemType.KeycardMTFCaptain, ItemType.KeycardChaosInsurgency, ItemType.KeycardO5, + }; + + /// + /// Gets an array of ItemTypes of each Site02 keycard. + /// + internal static ItemType[] AllSite02 { get; } = { ItemType.KeycardJanitor, ItemType.KeycardScientist, ItemType.KeycardResearchCoordinator, ItemType.KeycardContainmentEngineer }; + + /// + /// Gets an array of ItemTypes of each Management keycard. + /// + internal static ItemType[] AllManagement { get; } = { ItemType.KeycardZoneManager, ItemType.KeycardFacilityManager }; + + /// + /// Gets an array of ItemTypes of each MetalCase keycard. + /// + internal static ItemType[] AllMetalCase { get; } = { ItemType.KeycardGuard }; + + /// + /// Gets an array of ItemTypes of each TaskForce keycard. + /// + internal static ItemType[] AllTaskForce { get; } = { ItemType.KeycardMTFPrivate, ItemType.KeycardMTFOperative, ItemType.KeycardMTFCaptain }; + + /// + /// Finds a for a keycard by checking if keycard properties match. + /// + /// /// Whether to use design to check keycards. + /// Whether to use permissions to check keycards. + /// Whether to use colors to check keycards. + /// If 1 match is found, returns the matched value. Otherwise, returns .. + /// Unoptimized for now, but shouldn't be too bad. + public ItemType FindMatch(bool matchDesign, bool matchPerms, bool matchColors) + { + List matches = ListPool.Pool.Get(); + + ItemType[] toIterate = Type switch + { + _ when !matchDesign => AllKeycards, + ItemType.KeycardCustomSite02 => AllSite02, + ItemType.KeycardCustomManagement => AllManagement, + ItemType.KeycardCustomMetalCase => AllMetalCase, + ItemType.KeycardCustomTaskForce => AllTaskForce, + _ => throw new ArgumentOutOfRangeException(nameof(Type), Type.ToString()), + }; + + ILabelKeycard label1 = this as ILabelKeycard; + foreach (ItemType type in toIterate) + { + KeycardItem keycard = type.GetTemplate(); + + foreach (DetailBase detail in keycard.Details) + { + if (detail is PredefinedPermsDetail permsDetail) + { + if (matchPerms && permsDetail.Levels.Permissions != KeycardLevels.Permissions) + goto cont; + } + + if (label1 is not null && detail is TranslatedLabelDetail label2) + { + if (matchColors && label1.LabelColor != label2._textColor) + goto cont; + } + } + + goto add; + + cont: + continue; + + add: + matches.Add(type); + } + + ItemType value = matches.Count is not 1 ? ItemType.None : matches[0]; + + ListPool.Pool.Return(matches); + + return value; + } + + /// + /// Resyncs all properties of the keycard. + /// Gets called by all setters by default. + /// + public void Resync() + { + // we loveeeeeeeeeeeee NW static fields trusttttttttttttttt I'm not mad at allllllllllll + CustomPermsDetail._customLevels = KeycardLevels; + CustomPermsDetail._customColor = PermissionsColor; + + CustomItemNameDetail._customText = ItemName; + CustomTintDetail._customColor = Color; + + if (this is ILabelKeycard label) + { + CustomLabelDetail._customText = label.Label; + CustomLabelDetail._customColor = label.LabelColor; + } + + if (this is INameTagKeycard holder) + NametagDetail._customNametag = holder.NameTag; + + if (this is IWearKeycard wear) + CustomWearDetail._customWearLevel = wear.Wear; + + if (this is ISerialNumberKeycard serial) + CustomSerialNumberDetail._customVal = serial.SerialNumber; + + if (this is IRankKeycard rank) + CustomRankDetail._index = rank.Rank; + + MirrorExtensions.ResyncKeycardItem(this); + } + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} ={ItemName}= ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/KeycardData.cs b/EXILED/Exiled.API/Features/Items/Keycards/KeycardData.cs new file mode 100644 index 0000000000..c8d60cbd2e --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/KeycardData.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using UnityEngine; + + /// + /// A class containing all possible properties of any keycard that aren't stored by the server. + /// + public class KeycardData + { + /// + /// Gets or sets the permissions color of this . + /// + public Color32? PermissionsColor { get; set; } + + /// + /// Gets or sets the item name of this . + /// + public string ItemName { get; set; } + + /// + /// Gets or sets the color of this . + /// + public Color32? Color { get; set; } + + /// + /// Gets or sets the name tag of this . + /// + public string NameTag { get; set; } = string.Empty; + + /// + /// Gets or sets the label of this . + /// + public string Label { get; set; } = string.Empty; + + /// + /// Gets or sets the label color of this . + /// + public Color32? LabelColor { get; set; } + + /// + /// Gets or sets the wear of this . + /// + public byte Wear { get; set; } = byte.MaxValue; + + /// + /// Gets or sets the serial number of this . + /// + public string SerialNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the rank of this . + /// + public byte Rank { get; set; } = byte.MaxValue; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/ManagementKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/ManagementKeycard.cs new file mode 100644 index 0000000000..9b03fd5eca --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/ManagementKeycard.cs @@ -0,0 +1,88 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using Exiled.API.Interfaces.Keycards; + + using Interactables.Interobjects.DoorUtils; + + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + using UnityEngine; + + /// + /// Represents the Management Custom Keycard. + /// + public class ManagementKeycard : CustomKeycardItem, ILabelKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal ManagementKeycard(KeycardItem itemBase) + : base(itemBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal ManagementKeycard(ItemType type) + : base(type) + { + } + + /// + public string Label + { + get => DataDict[Serial].Label; + + set + { + DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => DataDict[Serial].LabelColor ?? Color.clear; + + set + { + DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// The new . + public static ManagementKeycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string label, Color labelColor) + { + ManagementKeycard keycard = Create(ItemType.KeycardCustomManagement); + keycard.KeycardLevels = keycardLevels; + keycard.PermissionsColor = permissionsColor; + keycard.ItemName = itemName; + keycard.Color = color; + keycard.Label = label; + keycard.LabelColor = labelColor; + return keycard; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs new file mode 100644 index 0000000000..ed7c963a2b --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs @@ -0,0 +1,128 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using Exiled.API.Interfaces.Keycards; + + using Interactables.Interobjects.DoorUtils; + + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + using UnityEngine; + + /// + /// Represents the Metal Custom Keycard. + /// + public class MetalKeycard : CustomKeycardItem, INameTagKeycard, ILabelKeycard, IWearKeycard, ISerialNumberKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal MetalKeycard(KeycardItem itemBase) + : base(itemBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal MetalKeycard(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => DataDict[Serial].NameTag; + set + { + DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string Label + { + get => DataDict[Serial].Label; + set + { + DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => DataDict[Serial].LabelColor ?? Color.clear; + set + { + DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Capped from 0-5 for Site-02 keycards, returns 255 if no wear level is found. + public byte Wear + { + get => DataDict[Serial].Wear; + set + { + DataDict[Serial].Wear = value; + + Resync(); + } + } + + /// + public string SerialNumber + { + get => DataDict[Serial].SerialNumber; + set + { + DataDict[Serial].SerialNumber = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// How worn the keycard looks (capped from 0-5). + /// The serial number of the keycard (numbers only, 12 max). + /// The new . + public static MetalKeycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear, string serialNumber) + { + MetalKeycard keycard = Create(ItemType.KeycardCustomManagement); + keycard.KeycardLevels = keycardLevels; + keycard.PermissionsColor = permissionsColor; + keycard.ItemName = itemName; + keycard.Color = color; + keycard.NameTag = nameTag; + keycard.Label = label; + keycard.LabelColor = labelColor; + keycard.Wear = wear; + keycard.SerialNumber = serialNumber; + return keycard; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/PermissionsProvider.cs b/EXILED/Exiled.API/Features/Items/Keycards/PermissionsProvider.cs new file mode 100644 index 0000000000..bbf6b31de7 --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/PermissionsProvider.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items; + using InventorySystem.Items.Autosync; + using InventorySystem.Items.Keycards; + using Mirror; + + /// + /// A simple class for providing permissions with . + /// + public class PermissionsProvider : IDoorPermissionProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The flags of this provider. + /// The to imitate. + /// The serial to imitate. + public PermissionsProvider(DoorPermissionFlags flags, ItemType type, ushort serial) + { + Flags = flags; + Type = type; + Serial = serial; + + PermissionsUsedCallback = (_, success) => + { + using (new AutosyncRpc(new ItemIdentifier(Type, Serial), out NetworkWriter writer)) + { + writer.WriteSubheader(KeycardItem.MsgType.OnKeycardUsed); + writer.WriteBool(success); + } + }; + } + + /// + /// Gets or sets the permissions of this provider. + /// + public DoorPermissionFlags Flags { get; set; } + + /// + /// Gets or sets the this imitates. + /// + public ItemType Type { get; set; } + + /// + /// Gets or sets the serial this imitates. + /// + public ushort Serial { get; set; } + + /// + /// Gets the action called when this is used. + /// + public PermissionUsed PermissionsUsedCallback { get; } + + /// + /// Gets the permissions of this . + /// + /// Not used. + /// The of this . + public DoorPermissionFlags GetPermissions(IDoorPermissionRequester requester) + { + return Flags; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/SingleUseKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/SingleUseKeycard.cs new file mode 100644 index 0000000000..0caff46af8 --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/SingleUseKeycard.cs @@ -0,0 +1,126 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using System.Collections.Generic; + + using Exiled.API.Enums; + using Exiled.API.Features.Pickups; + using Exiled.API.Features.Pickups.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + /// + /// A base class for SingleUse keycard items. + /// + public class SingleUseKeycard : Keycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal SingleUseKeycard(SingleUseKeycardItem itemBase) + : base(itemBase) + { + Base = itemBase; + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal SingleUseKeycard(ItemType type) + : this((SingleUseKeycardItem)Server.Host.Inventory.CreateItemInstance(new(type, 0), false)) + { + } + + /// + /// Gets the this encapsulates. + /// + public new SingleUseKeycardItem Base { get; } + + /// + /// Gets or sets the amount of uses remaining on the keycard. + /// + /// Multiple uses can look strange for clients (they'll run the discard animation, but can reselect the keycard) + ///

+ /// Setting this to 0 or lower will destroy the keycard. + ///
+ public int Uses + { + get => RemainingUses.TryGetValue(Serial, out int value) ? value : 1; + set + { + if (value <= 0) + { + IsDestroyed = true; + } + + RemainingUses[Serial] = value; + } + } + + /// + /// Gets or sets the time delay to destroy the Keycard after being used. + /// + public float TimeToDestroy + { + get => Base._timeToDestroy; + set => Base._timeToDestroy = value; + } + + /// + public override KeycardPermissions Permissions + { + get => (KeycardPermissions)Base._singleUsePermissions; + set => Base._singleUsePermissions = (DoorPermissionFlags)value; + } + + /// + /// Gets or sets a value indicating whether the Keycard allow the closing of Doors. + /// + public bool AllowClosingDoors + { + get => Base._allowClosingDoors; + set => Base._allowClosingDoors = value; + } + + /// + /// Gets or sets a value indicating whether the Keycard is destroyed. + /// + public bool IsDestroyed + { + get => Base._destroyed; + set => Base._destroyed = value; + } + + /// + /// Gets a dictionary storing all remaining uses for every . + /// + internal static Dictionary RemainingUses { get; } = new(); + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} ={AllowClosingDoors}= ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + + /// + internal override void ReadPickupInfoBefore(Pickup pickup) + { + if (pickup is SingleUseKeycardPickup singleUseKeycard) + { + Uses = singleUseKeycard.Uses; + TimeToDestroy = singleUseKeycard.TimeToDestroy; + Permissions = singleUseKeycard.Permissions; + AllowClosingDoors = singleUseKeycard.AllowClosingDoors; + } + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs new file mode 100644 index 0000000000..5cdf7d842b --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs @@ -0,0 +1,114 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using Exiled.API.Interfaces.Keycards; + + using Interactables.Interobjects.DoorUtils; + + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + using UnityEngine; + + /// + /// Represents the Site-02 Custom Keycard. + /// + public class Site02Keycard : CustomKeycardItem, INameTagKeycard, ILabelKeycard, IWearKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal Site02Keycard(KeycardItem itemBase) + : base(itemBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal Site02Keycard(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => DataDict[Serial].NameTag; + set + { + DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string Label + { + get => DataDict[Serial].Label; + set + { + DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => DataDict[Serial].LabelColor ?? Color.clear; + set + { + DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Capped from 0-4 for Site-02 keycards, returns 255 if no wear level is found. + public byte Wear + { + get => DataDict[Serial].Wear; + set + { + DataDict[Serial].Wear = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// How worn the keycard looks (capped from 0-5). + /// The new . + public static Site02Keycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear) + { + Site02Keycard keycard = Create(ItemType.KeycardCustomManagement); + keycard.KeycardLevels = keycardLevels; + keycard.PermissionsColor = permissionsColor; + keycard.ItemName = itemName; + keycard.Color = color; + keycard.NameTag = nameTag; + keycard.Label = label; + keycard.LabelColor = labelColor; + keycard.Wear = wear; + return keycard; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs new file mode 100644 index 0000000000..8775ee0e98 --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs @@ -0,0 +1,101 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.Keycards +{ + using Exiled.API.Interfaces.Keycards; + + using Interactables.Interobjects.DoorUtils; + + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + using UnityEngine; + + /// + /// Represents the Task Force Custom Keycard. + /// + public class TaskForceKeycard : CustomKeycardItem, INameTagKeycard, ISerialNumberKeycard, IRankKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal TaskForceKeycard(KeycardItem itemBase) + : base(itemBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the item to create. + internal TaskForceKeycard(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => DataDict[Serial].NameTag; + set + { + DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string SerialNumber + { + get => DataDict[Serial].SerialNumber; + set + { + DataDict[Serial].SerialNumber = value; + + Resync(); + } + } + + /// + public byte Rank + { + get => DataDict[Serial].Rank; + set + { + DataDict[Serial].Rank = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The serial number of the keycard (numbers only, 12 max). + /// The rank of the keycard (capped from 0-3). + /// The new . + public static TaskForceKeycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string serialNumber, byte rank) + { + TaskForceKeycard keycard = Create(ItemType.KeycardCustomManagement); + keycard.KeycardLevels = keycardLevels; + keycard.PermissionsColor = permissionsColor; + keycard.ItemName = itemName; + keycard.Color = color; + keycard.NameTag = nameTag; + keycard.SerialNumber = serialNumber; + keycard.Rank = rank; + return keycard; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 7e7208cbc6..8eb89e0226 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -19,6 +19,7 @@ namespace Exiled.API.Features using Enums; using Exiled.API.Extensions; using Exiled.API.Features.Hazards; + using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups; using Exiled.API.Features.Toys; using InventorySystem; @@ -418,6 +419,10 @@ internal static void ClearCache() Firearm.BaseCodesValue.Clear(); Firearm.AvailableAttachmentsValue.Clear(); + CustomKeycardItem.DataDict.Clear(); + + SingleUseKeycard.RemainingUses.Clear(); + #pragma warning disable CS0618 Scp559.CakeToWrapper.Clear(); diff --git a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs index e7e29ff13e..fa087b7ab1 100644 --- a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs @@ -9,6 +9,7 @@ namespace Exiled.API.Features.Pickups { using Exiled.API.Enums; using Exiled.API.Features.Items; + using Exiled.API.Features.Items.Keycards; using Exiled.API.Interfaces; using InventorySystem.Items; @@ -29,6 +30,10 @@ internal KeycardPickup(BaseKeycard pickupBase) : base(pickupBase) { Base = pickupBase; + if (Base is null) + { + Log.Info(GetType()); + } } /// @@ -42,9 +47,9 @@ internal KeycardPickup(ItemType type) } /// - /// Gets the of the keycard. + /// Gets or sets the of the keycard. /// - public KeycardPermissions Permissions { get; private set; } + public virtual KeycardPermissions Permissions { get; set; } /// /// Gets the that this class is encapsulating. @@ -55,7 +60,7 @@ internal KeycardPickup(ItemType type) internal override void ReadItemInfo(Item item) { base.ReadItemInfo(item); - if (item is Keycard keycarditem) + if (item is Keycard keycarditem and not CustomKeycardItem) { Permissions = keycarditem.Permissions; } diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/ChaosKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/ChaosKeycardPickup.cs new file mode 100644 index 0000000000..09374e3bc5 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/ChaosKeycardPickup.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Keycards.Snake; + using InventorySystem.Items.Pickups; + + /// + /// A base class for all keycard pickups. + /// + public class ChaosKeycardPickup : Pickups.KeycardPickup + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal ChaosKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal ChaosKeycardPickup(ItemType type) + : base(type) + { + } + + /// + /// Gets the this encapsulates. + /// + public SnakeEngine SnakeEngine => ChaosKeycardItem.GetEngineForSerial(Serial); + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} == ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs new file mode 100644 index 0000000000..e2c9374ddc --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs @@ -0,0 +1,230 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Features.Pools; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// A base class for all keycard pickups. + /// + public abstract class CustomKeycardPickup : Pickups.KeycardPickup + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal CustomKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + if (!CustomKeycardItem.DataDict.ContainsKey(Serial)) + CustomKeycardItem.DataDict[Serial] = new KeycardData(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal CustomKeycardPickup(ItemType type) + : base(type) + { + } + + /// + /// Gets or sets the permissions this keycard has. + /// + public override KeycardPermissions Permissions + { + get => CustomPermsDetail.CustomPermissions.TryGetValue(Serial, out DoorPermissionFlags flags) ? (KeycardPermissions)flags : KeycardPermissions.None; + set + { + CustomPermsDetail.CustomPermissions[Serial] = (DoorPermissionFlags)value; + + Resync(); + } + } + + /// + /// Gets or sets the permissions this keycard has. + /// + public KeycardLevels KeycardLevels + { + get => new((DoorPermissionFlags)Permissions); + set + { + CustomPermsDetail.CustomPermissions[Serial] = value.Permissions; + + Resync(); + } + } + + /// + /// Gets or sets the color of this keycard's permissions. + /// + public Color PermissionsColor + { + get => CustomKeycardItem.DataDict[Serial].PermissionsColor ?? Color.clear; + set + { + CustomKeycardItem.DataDict[Serial].PermissionsColor = value; + + Resync(); + } + } + + /// + /// Gets or sets the name of this keycard within the inventory. + /// + public string ItemName + { + get => CustomKeycardItem.DataDict[Serial].ItemName; + set + { + CustomKeycardItem.DataDict[Serial].ItemName = value; + + Resync(); + } + } + + /// + /// Gets or sets the color of this keycard. + /// + public Color Color + { + get => CustomKeycardItem.DataDict[Serial].Color ?? Color.clear; + set + { + CustomKeycardItem.DataDict[Serial].Color = value; + + Resync(); + } + } + + /// + /// Finds a for a keycard by checking if keycard properties match. + /// + /// /// Whether to use design to check keycards. + /// Whether to use permissions to check keycards. + /// Whether to use colors to check keycards. + /// If 1 match is found, returns the matched value. Otherwise, returns .. + /// Unoptimized for now, but shouldn't be too bad. + public ItemType FindMatch(bool matchDesign, bool matchPerms, bool matchColors) + { + List matches = ListPool.Pool.Get(); + + ItemType[] toIterate = Type switch + { + _ when !matchDesign => CustomKeycardItem.AllKeycards, + ItemType.KeycardCustomSite02 => CustomKeycardItem.AllSite02, + ItemType.KeycardCustomManagement => CustomKeycardItem.AllManagement, + ItemType.KeycardCustomMetalCase => CustomKeycardItem.AllMetalCase, + ItemType.KeycardCustomTaskForce => CustomKeycardItem.AllTaskForce, + _ => throw new ArgumentOutOfRangeException(nameof(Type), Type.ToString()), + }; + + ILabelKeycard label1 = this as ILabelKeycard; + foreach (ItemType type in toIterate) + { + KeycardItem keycard = type.GetTemplate(); + + foreach (DetailBase detail in keycard.Details) + { + if (detail is PredefinedPermsDetail permsDetail) + { + if (matchPerms && permsDetail.Levels.Permissions != KeycardLevels.Permissions) + goto cont; + } + + if (label1 is not null && detail is TranslatedLabelDetail label2) + { + if (matchColors && label1.LabelColor != label2._textColor) + goto cont; + } + } + + goto add; + + cont: + continue; + + add: + matches.Add(type); + } + + ItemType value = matches.Count is not 1 ? ItemType.None : matches[0]; + + ListPool.Pool.Return(matches); + + return value; + } + + /// + /// Resyncs all properties of the keycard. + /// Gets called by all setters by default. + /// + public void Resync() + { + // we loveeeeeeeeeeeee NW static fields trusttttttttttttttt I'm not mad at allllllllllll + CustomPermsDetail._customLevels = KeycardLevels; + CustomPermsDetail._customColor = PermissionsColor; + + CustomItemNameDetail._customText = ItemName; + CustomTintDetail._customColor = Color; + + if (this is ILabelKeycard label) + { + CustomLabelDetail._customText = label.Label; + CustomLabelDetail._customColor = label.LabelColor; + } + + if (this is INameTagKeycard holder) + NametagDetail._customNametag = holder.NameTag; + + if (this is IWearKeycard wear) + CustomWearDetail._customWearLevel = wear.Wear; + + if (this is ISerialNumberKeycard serial) + CustomSerialNumberDetail._customVal = serial.SerialNumber; + + if (this is IRankKeycard rank) + CustomRankDetail._index = rank.Rank; + + // can happen if a dev does the big dumb + if (Base is null) + { + Log.Error($"Base of CustomKeycardPickup was null! See StackTrace to fix problem.\n{GetType()}\n{new StackTrace()}"); + } + + MirrorExtensions.ResyncKeycardPickup(this); + } + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} ={ItemName}= ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + + /// + protected override void InitializeProperties(ItemBase itemBase) + { + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs new file mode 100644 index 0000000000..c2adc99aa0 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs @@ -0,0 +1,84 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Represents the Management Custom Keycard. + /// + public class ManagementKeycardPickup : CustomKeycardPickup, ILabelKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal ManagementKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal ManagementKeycardPickup(ItemType type) + : base(type) + { + } + + /// + public string Label + { + get => CustomKeycardItem.DataDict[Serial].Label; + set + { + CustomKeycardItem.DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => CustomKeycardItem.DataDict[Serial].LabelColor ?? Color.clear; + set + { + CustomKeycardItem.DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// The new . + public static ManagementKeycardPickup Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string label, Color labelColor) + { + ManagementKeycardPickup keycardPickup = Create(ItemType.KeycardCustomManagement); + keycardPickup.KeycardLevels = keycardLevels; + keycardPickup.PermissionsColor = permissionsColor; + keycardPickup.ItemName = itemName; + keycardPickup.Color = color; + keycardPickup.Label = label; + keycardPickup.LabelColor = labelColor; + return keycardPickup; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/MetalKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/MetalKeycardPickup.cs new file mode 100644 index 0000000000..cf1c0ea894 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/MetalKeycardPickup.cs @@ -0,0 +1,126 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Represents the Metal Custom Keycard. + /// + public class MetalKeycardPickup : CustomKeycardPickup, INameTagKeycard, ILabelKeycard, IWearKeycard, ISerialNumberKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal MetalKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal MetalKeycardPickup(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => CustomKeycardItem.DataDict[Serial].NameTag; + set + { + CustomKeycardItem.DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string Label + { + get => CustomKeycardItem.DataDict[Serial].Label; + set + { + CustomKeycardItem.DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => CustomKeycardItem.DataDict[Serial].LabelColor ?? Color.clear; + set + { + CustomKeycardItem.DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Capped from 0-5 for Site-02 keycards, returns 255 if no wear level is found. + public byte Wear + { + get => CustomKeycardItem.DataDict[Serial].Wear; + set + { + CustomKeycardItem.DataDict[Serial].Wear = value; + + Resync(); + } + } + + /// + public string SerialNumber + { + get => CustomKeycardItem.DataDict[Serial].SerialNumber; + set + { + CustomKeycardItem.DataDict[Serial].SerialNumber = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// How worn the keycard looks (capped from 0-5). + /// The serial number of the keycard (numbers only, 12 max). + /// The new . + public static MetalKeycardPickup Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear, string serialNumber) + { + MetalKeycardPickup keycardPickup = Create(ItemType.KeycardCustomMetalCase); + keycardPickup.KeycardLevels = keycardLevels; + keycardPickup.PermissionsColor = permissionsColor; + keycardPickup.ItemName = itemName; + keycardPickup.Color = color; + keycardPickup.NameTag = nameTag; + keycardPickup.Label = label; + keycardPickup.LabelColor = labelColor; + keycardPickup.Wear = wear; + keycardPickup.SerialNumber = serialNumber; + return keycardPickup; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/SingleUseKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/SingleUseKeycardPickup.cs new file mode 100644 index 0000000000..8a79b0d405 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/SingleUseKeycardPickup.cs @@ -0,0 +1,87 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using Exiled.API.Enums; + using Exiled.API.Features.Items; + using Exiled.API.Features.Items.Keycards; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + + /// + /// A base class for all keycard pickups. + /// + public class SingleUseKeycardPickup : Pickups.KeycardPickup + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal SingleUseKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal SingleUseKeycardPickup(ItemType type) + : base(type) + { + } + + /// + public int Uses { get; set; } = 1; + + /// + /// Gets or sets the time delay to destroy the Keycard after being used. + /// + public float TimeToDestroy { get; set; } + + /// + public override KeycardPermissions Permissions { get; set; } + + /// + /// Gets or sets a value indicating whether the Keycard allow the closing of Doors. + /// + public bool AllowClosingDoors { get; set; } + + /// + /// Returns the Keycard in a human readable format. + /// + /// A string containing Keycard-related data. + public override string ToString() => $"{Type} ={AllowClosingDoors}= ({Serial}) [{Weight}] *{Scale}* |{Permissions}|"; + + /// + internal override void ReadItemInfo(Item item) + { + base.ReadItemInfo(item); + if (item is SingleUseKeycard singleUseKeycardItem) + { + Uses = singleUseKeycardItem.Uses; + TimeToDestroy = singleUseKeycardItem.TimeToDestroy; + Permissions = singleUseKeycardItem.Permissions; + AllowClosingDoors = singleUseKeycardItem.AllowClosingDoors; + } + } + + /// + protected override void InitializeProperties(ItemBase itemBase) + { + base.InitializeProperties(itemBase); + if (itemBase is SingleUseKeycardItem singleUseKeycardItem) + { + TimeToDestroy = singleUseKeycardItem._timeToDestroy; + Permissions = (KeycardPermissions)singleUseKeycardItem._singleUsePermissions; + AllowClosingDoors = singleUseKeycardItem._allowClosingDoors; + } + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/Site02KeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/Site02KeycardPickup.cs new file mode 100644 index 0000000000..162ec6a721 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/Site02KeycardPickup.cs @@ -0,0 +1,112 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Represents the Site-02 Custom Keycard. + /// + public class Site02KeycardPickup : CustomKeycardPickup, INameTagKeycard, ILabelKeycard, IWearKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal Site02KeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal Site02KeycardPickup(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => CustomKeycardItem.DataDict[Serial].NameTag; + set + { + CustomKeycardItem.DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string Label + { + get => CustomKeycardItem.DataDict[Serial].Label; + set + { + CustomKeycardItem.DataDict[Serial].Label = value; + Resync(); + } + } + + /// + public Color LabelColor + { + get => CustomKeycardItem.DataDict[Serial].LabelColor ?? Color.clear; + set + { + CustomKeycardItem.DataDict[Serial].LabelColor = value; + Resync(); + } + } + + /// + /// Capped from 0-4 for Site-02 keycards, returns 255 if no wear level is found. + public byte Wear + { + get => CustomKeycardItem.DataDict[Serial].Wear; + set + { + CustomKeycardItem.DataDict[Serial].Wear = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The label on the keycard. + /// The color of the label on the keycard. + /// How worn the keycard looks (capped from 0-5). + /// The new . + public static Site02KeycardPickup Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear) + { + Site02KeycardPickup keycardPickup = Create(ItemType.KeycardCustomSite02); + keycardPickup.KeycardLevels = keycardLevels; + keycardPickup.PermissionsColor = permissionsColor; + keycardPickup.ItemName = itemName; + keycardPickup.Color = color; + keycardPickup.NameTag = nameTag; + keycardPickup.Label = label; + keycardPickup.LabelColor = labelColor; + keycardPickup.Wear = wear; + return keycardPickup; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/TaskForceKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/TaskForceKeycardPickup.cs new file mode 100644 index 0000000000..a6874f57d3 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/TaskForceKeycardPickup.cs @@ -0,0 +1,99 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups.Keycards +{ + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Interfaces.Keycards; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Represents the Task Force Custom Keycard. + /// + public class TaskForceKeycardPickup : CustomKeycardPickup, INameTagKeycard, ISerialNumberKeycard, IRankKeycard + { + /// + /// Initializes a new instance of the class. + /// + /// The to encapsulate. + internal TaskForceKeycardPickup(KeycardPickup pickupBase) + : base(pickupBase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the pickup to create. + internal TaskForceKeycardPickup(ItemType type) + : base(type) + { + } + + /// + public string NameTag + { + get => CustomKeycardItem.DataDict[Serial].NameTag; + set + { + CustomKeycardItem.DataDict[Serial].NameTag = value; + Resync(); + } + } + + /// + public string SerialNumber + { + get => CustomKeycardItem.DataDict[Serial].SerialNumber; + set + { + CustomKeycardItem.DataDict[Serial].SerialNumber = value; + + Resync(); + } + } + + /// + public byte Rank + { + get => CustomKeycardItem.DataDict[Serial].Rank; + set + { + CustomKeycardItem.DataDict[Serial].Rank = value; + + Resync(); + } + } + + /// + /// Creates a . + /// + /// The permissions of the keycard. + /// The color of the permissions of the keycard. + /// The inventory name of the keycard. + /// The color of the keycard. + /// The name of the owner of the keycard. + /// The serial number of the keycard (numbers only, 12 max). + /// The rank of the keycard (capped from 0-3). + /// The new . + public static TaskForceKeycardPickup Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string serialNumber, byte rank) + { + TaskForceKeycardPickup keycardPickup = Create(ItemType.KeycardCustomTaskForce); + keycardPickup.KeycardLevels = keycardLevels; + keycardPickup.PermissionsColor = permissionsColor; + keycardPickup.ItemName = itemName; + keycardPickup.Color = color; + keycardPickup.NameTag = nameTag; + keycardPickup.SerialNumber = serialNumber; + keycardPickup.Rank = rank; + return keycardPickup; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index a610217fb3..ef1106cf46 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -13,6 +13,7 @@ namespace Exiled.API.Features.Pickups using Exiled.API.Extensions; using Exiled.API.Features.Core; + using Exiled.API.Features.Pickups.Keycards; using Exiled.API.Features.Pickups.Projectiles; using Exiled.API.Interfaces; @@ -317,7 +318,16 @@ public static Pickup Get(ItemPickupBase pickupBase) _ => new GrenadePickup(timeGrenade), }, BaseFirearmPickup firearmPickup => new FirearmPickup(firearmPickup), - BaseKeycardPickup keycardPickup => new KeycardPickup(keycardPickup), + BaseKeycardPickup keycardPickup => keycardPickup.NetworkInfo.ItemId switch + { + ItemType.KeycardCustomTaskForce => new TaskForceKeycardPickup(keycardPickup), + ItemType.KeycardCustomSite02 => new Site02KeycardPickup(keycardPickup), + ItemType.KeycardCustomManagement => new ManagementKeycardPickup(keycardPickup), + ItemType.KeycardCustomMetalCase => new MetalKeycardPickup(keycardPickup), + ItemType.SurfaceAccessPass => new SingleUseKeycardPickup(keycardPickup), + ItemType.KeycardChaosInsurgency => new ChaosKeycardPickup(keycardPickup), + _ => new KeycardPickup(keycardPickup), + }, BaseBodyArmorPickup bodyArmorPickup => new BodyArmorPickup(bodyArmorPickup), BaseScp330Pickup scp330Pickup => new Scp330Pickup(scp330Pickup), BaseScp1576Pickup scp1576Pickup => new Scp1576Pickup(scp1576Pickup), @@ -487,7 +497,16 @@ public static IEnumerable Get(IEnumerable gameObjects) _ => new GrenadePickup(type), }, BaseFirearmPickup => new FirearmPickup(type), - BaseKeycardPickup => new KeycardPickup(type), + BaseKeycardPickup keycardPickup => type switch + { + ItemType.KeycardCustomTaskForce => new TaskForceKeycardPickup(type), + ItemType.KeycardCustomSite02 => new Site02KeycardPickup(type), + ItemType.KeycardCustomManagement => new ManagementKeycardPickup(type), + ItemType.KeycardCustomMetalCase => new MetalKeycardPickup(type), + ItemType.SurfaceAccessPass => new SingleUseKeycardPickup(type), + ItemType.KeycardChaosInsurgency => new ChaosKeycardPickup(type), + _ => new KeycardPickup(keycardPickup), + }, BaseBodyArmorPickup => new BodyArmorPickup(type), BaseScp330Pickup => new Scp330Pickup(), BaseScp1576Pickup => new Scp1576Pickup(), diff --git a/EXILED/Exiled.API/Interfaces/Keycards/ILabelKeycard.cs b/EXILED/Exiled.API/Interfaces/Keycards/ILabelKeycard.cs new file mode 100644 index 0000000000..d724f997ea --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/Keycards/ILabelKeycard.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces.Keycards +{ + using Exiled.API.Features.Items.Keycards; + using UnityEngine; + + /// + /// An interface for 's with the property. + /// + public interface ILabelKeycard + { + /// + /// Gets or sets the label of this . + /// + public string Label { get; set; } + + /// + /// Gets or sets the of this . + /// + public Color LabelColor { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Interfaces/Keycards/INameTagKeycard.cs b/EXILED/Exiled.API/Interfaces/Keycards/INameTagKeycard.cs new file mode 100644 index 0000000000..02d85ddd53 --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/Keycards/INameTagKeycard.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces.Keycards +{ + using Exiled.API.Features.Items.Keycards; + + /// + /// An interface for 's with the property. + /// + public interface INameTagKeycard + { + /// + /// Gets or sets the name of the owner of this . + /// + public string NameTag { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Interfaces/Keycards/IRankKeycard.cs b/EXILED/Exiled.API/Interfaces/Keycards/IRankKeycard.cs new file mode 100644 index 0000000000..f0bb1d389a --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/Keycards/IRankKeycard.cs @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces.Keycards +{ + using Exiled.API.Features.Items.Keycards; + + /// + /// An interface for 's with the property. + /// + public interface IRankKeycard + { + /// + /// Gets or sets the rank of this . + /// + /// Capped from 0-3, returns 255 if no value was found. + public byte Rank { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Interfaces/Keycards/ISerialNumberKeycard.cs b/EXILED/Exiled.API/Interfaces/Keycards/ISerialNumberKeycard.cs new file mode 100644 index 0000000000..c3e4c799b2 --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/Keycards/ISerialNumberKeycard.cs @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces.Keycards +{ + using Exiled.API.Features.Items.Keycards; + + /// + /// An interface for 's with the property. + /// + public interface ISerialNumberKeycard + { + /// + /// Gets or sets the serial number of this . + /// + /// Can only hold 12 numbers. Non-numerical chars will be replaced with "-". + public string SerialNumber { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Interfaces/Keycards/IWearKeycard.cs b/EXILED/Exiled.API/Interfaces/Keycards/IWearKeycard.cs new file mode 100644 index 0000000000..02f4584b38 --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/Keycards/IWearKeycard.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces.Keycards +{ + using Exiled.API.Features.Items.Keycards; + + /// + /// An interface for 's with the property. + /// + public interface IWearKeycard + { + /// + /// Gets or sets the wear on this . + /// + public byte Wear { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Fixes/NameTagDetailFix.cs b/EXILED/Exiled.Events/Patches/Fixes/NameTagDetailFix.cs new file mode 100644 index 0000000000..4f766c9b4f --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/NameTagDetailFix.cs @@ -0,0 +1,57 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Extensions; + using Exiled.API.Features.Pools; + using HarmonyLib; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + using Mirror; + + using static HarmonyLib.AccessTools; + + /// + /// Fixes NameTagDetail not using custom value if Custom Keycard was spawned as pickup. + /// + [HarmonyPatch(typeof(NametagDetail), nameof(NametagDetail.WriteNewPickup))] + public class NameTagDetailFix + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label label = generator.DefineLabel(); + + newInstructions[0].WithLabels(label); + newInstructions.InsertRange(0, new CodeInstruction[] + { + // if (pickup.ItemId.TypeId.IsCustomKeycard) + new(OpCodes.Ldarg_1), + new(OpCodes.Callvirt, PropertyGetter(typeof(KeycardPickup), nameof(KeycardPickup.ItemId))), + new(OpCodes.Ldfld, Field(typeof(ItemIdentifier), nameof(ItemIdentifier.TypeId))), + new(OpCodes.Call, Method(typeof(ItemExtensions), nameof(ItemExtensions.IsCustomKeycard))), + new(OpCodes.Brfalse, label), + + // writer.WriteString(_customNametag); + new(OpCodes.Ldarg_2), + new(OpCodes.Ldsfld, Field(typeof(NametagDetail), nameof(NametagDetail._customNametag))), + new(OpCodes.Call, Method(typeof(NetworkWriterExtensions), nameof(NetworkWriterExtensions.WriteString))), + new(OpCodes.Ret), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Fixes/ThrownCustomKeycardFix.cs b/EXILED/Exiled.Events/Patches/Fixes/ThrownCustomKeycardFix.cs new file mode 100644 index 0000000000..24feae442f --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/ThrownCustomKeycardFix.cs @@ -0,0 +1,100 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Extensions; + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Features.Pools; + using HarmonyLib; + using Interactables.Interobjects.DoorUtils; + using InventorySystem.Items.Keycards; + using InventorySystem.Items.Pickups; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to fix custom keycards with custom permissions not working. + /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/1625). + /// + [HarmonyPatch(typeof(KeycardPickup), nameof(KeycardPickup.ProcessCollision))] + public class ThrownCustomKeycardFix + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder permissions = generator.DeclareLocal(typeof(DoorPermissionFlags)); + + LocalBuilder type = generator.DeclareLocal(typeof(ItemType)); + LocalBuilder serial = generator.DeclareLocal(typeof(ushort)); + + Label newDefault = generator.DefineLabel(); + + // index before this.Info.ItemId.TryGetTemplate(out provider); + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ldflda) - 1; + + // get label of start of TryGetTemplate from earlier brtrue + object runDefault = newInstructions[index - 2].operand; + newInstructions[index - 2].operand = newDefault; + + // get label of after TryGetTemplate from later brtrue + object skipDefault = newInstructions[index + 5].operand; + + newInstructions.InsertRange(index, new[] + { + // type = this.Info.ItemId; + new CodeInstruction(OpCodes.Ldarg_0).WithLabels(newDefault), + new(OpCodes.Ldflda, Field(typeof(KeycardPickup), nameof(KeycardPickup.Info))), + new(OpCodes.Ldfld, Field(typeof(PickupSyncInfo), nameof(PickupSyncInfo.ItemId))), + new(OpCodes.Stloc, type), + + // type.IsCustomKeycard(); + new(OpCodes.Ldloc, type), + new(OpCodes.Call, Method(typeof(ItemExtensions), nameof(ItemExtensions.IsCustomKeycard))), + + // jump to default execution if keycard is not custom + new(OpCodes.Brfalse, runDefault), + + // load dictionary + new(OpCodes.Ldsfld, Field(typeof(CustomPermsDetail), nameof(CustomPermsDetail.CustomPermissions))), + + // serial = this.Info.Serial; + new(OpCodes.Ldarg_0), + new(OpCodes.Ldflda, Field(typeof(KeycardPickup), nameof(KeycardPickup.Info))), + new(OpCodes.Ldfld, Field(typeof(PickupSyncInfo), nameof(PickupSyncInfo.Serial))), + new(OpCodes.Stloc, serial), + + // Dictionary.TryGetValue(serial, out DoorPermissionFlags permissions); + new(OpCodes.Ldloc, serial), + new(OpCodes.Ldloca, permissions), + new(OpCodes.Callvirt, Method(typeof(Dictionary), nameof(Dictionary.TryGetValue))), + + // go to normal execution if no custom perms found + new(OpCodes.Brfalse, runDefault), + + // provider = new PermissionsProvider(permissions, type, serial); + new(OpCodes.Ldloc, permissions), + new(OpCodes.Ldloc, type), + new(OpCodes.Ldloc, serial), + new(OpCodes.Newobj, Constructor(typeof(PermissionsProvider), new[] { typeof(DoorPermissionFlags), typeof(ItemType), typeof(ushort) })), + new(OpCodes.Stloc_2), + + // skip past TryGetTemplate + new(OpCodes.Br, skipDefault), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomItemNameDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomItemNameDetailData.cs new file mode 100644 index 0000000000..896084a08a --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomItemNameDetailData.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing item name from custom keycards. + /// + [HarmonyPatch(typeof(CustomItemNameDetail))] + public class CustomItemNameDetailData + { + [HarmonyPatch(nameof(CustomItemNameDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.ItemName = CustomItemNameDetail._customText; + } + + [HarmonyPatch(nameof(CustomItemNameDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.ItemName = CustomItemNameDetail._customText; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomLabelDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomLabelDetailData.cs new file mode 100644 index 0000000000..574ce2b78d --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomLabelDetailData.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing label and label color from custom keycards. + /// + [HarmonyPatch(typeof(CustomLabelDetail))] + public class CustomLabelDetailData + { + [HarmonyPatch(nameof(CustomLabelDetail.WriteNewItem))] + [HarmonyPrefix] + private static void Prefix(KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.Label = CustomLabelDetail._customText; + data.LabelColor = CustomLabelDetail._customColor; + } + + [HarmonyPatch(nameof(CustomLabelDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void Prefix(KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.Label = CustomLabelDetail._customText; + data.LabelColor = CustomLabelDetail._customColor; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomPermsDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomPermsDetailData.cs new file mode 100644 index 0000000000..d3a1c7cc84 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomPermsDetailData.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing permission colors from custom keycards. + /// + [HarmonyPatch(typeof(CustomPermsDetail), nameof(CustomPermsDetail.WriteCustom))] + public class CustomPermsDetailData + { + private static void Prefix(IIdentifierProvider target) + { + if (!CustomKeycardItem.DataDict.TryGetValue(target.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[target.ItemId.SerialNumber] = data = new KeycardData(); + data.PermissionsColor = CustomPermsDetail._customColor; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomRankDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomRankDetailData.cs new file mode 100644 index 0000000000..6b5889f193 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomRankDetailData.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + using UnityEngine; + +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + + /// + /// Patch for storing rank from custom keycards. + /// + [HarmonyPatch(typeof(CustomRankDetail))] + public class CustomRankDetailData + { + [HarmonyPatch(nameof(CustomRankDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(CustomRankDetail __instance, KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.Rank = (byte)(Mathf.Abs(CustomRankDetail._index) % __instance._options.Length); + } + + [HarmonyPatch(nameof(CustomRankDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(CustomRankDetail __instance, KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.Rank = (byte)(Mathf.Abs(CustomRankDetail._index) % __instance._options.Length); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomSerialNumberDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomSerialNumberDetailData.cs new file mode 100644 index 0000000000..b846455d9c --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomSerialNumberDetailData.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing serial numbers from custom keycards. + /// + [HarmonyPatch(typeof(CustomSerialNumberDetail))] + public class CustomSerialNumberDetailData + { + [HarmonyPatch(nameof(CustomSerialNumberDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.SerialNumber = CustomSerialNumberDetail._customVal; + } + + [HarmonyPatch(nameof(CustomSerialNumberDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.SerialNumber = CustomSerialNumberDetail._customVal; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomTintDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomTintDetailData.cs new file mode 100644 index 0000000000..fb2bf92181 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomTintDetailData.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing colors from custom keycards. + /// + [HarmonyPatch(typeof(CustomTintDetail))] + public class CustomTintDetailData + { + [HarmonyPatch(nameof(CustomTintDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.Color = CustomTintDetail._customColor; + } + + [HarmonyPatch(nameof(CustomTintDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.Color = CustomTintDetail._customColor; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomWearDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomWearDetailData.cs new file mode 100644 index 0000000000..af25a81e9e --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/CustomWearDetailData.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing wear from custom keycards. + /// + [HarmonyPatch(typeof(CustomWearDetail))] + public class CustomWearDetailData + { + [HarmonyPatch(nameof(CustomWearDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(KeycardItem item) + { + if (!CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + CustomKeycardItem.DataDict[item.ItemSerial] = data = new KeycardData(); + data.Wear = CustomWearDetail._customWearLevel; + } + + [HarmonyPatch(nameof(CustomWearDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(KeycardPickup pickup) + { + if (!CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + CustomKeycardItem.DataDict[pickup.ItemId.SerialNumber] = data = new KeycardData(); + data.Wear = CustomWearDetail._customWearLevel; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/NameTagDetailData.cs b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/NameTagDetailData.cs new file mode 100644 index 0000000000..482c996c3c --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/KeycardDetails/NameTagDetailData.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.KeycardDetails +{ + using Exiled.API.Features.Items.Keycards; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + /// + /// Patch for storing name tags from custom keycards. + /// + /// There's a fix for . + [HarmonyPatch(typeof(NametagDetail))] + public class NameTagDetailData + { + [HarmonyPatch(nameof(NametagDetail.WriteNewItem))] + [HarmonyPrefix] + private static void PrefixItem(KeycardItem item) + { + if (CustomKeycardItem.DataDict.TryGetValue(item.ItemSerial, out KeycardData data)) + data.NameTag = NametagDetail._customNametag; + } + + [HarmonyPatch(nameof(NametagDetail.WriteNewPickup))] + [HarmonyPrefix] + private static void PrefixPickup(KeycardPickup pickup) + { + if (CustomKeycardItem.DataDict.TryGetValue(pickup.ItemId.SerialNumber, out KeycardData data)) + data.NameTag = NametagDetail._customNametag; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/SingleUseKeycardRemainingUses.cs b/EXILED/Exiled.Events/Patches/Generic/SingleUseKeycardRemainingUses.cs new file mode 100644 index 0000000000..c14b89b770 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/SingleUseKeycardRemainingUses.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Features.Pools; + using HarmonyLib; + using InventorySystem.Items.Keycards; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// + [HarmonyPatch(typeof(SingleUseKeycardItem), nameof(SingleUseKeycardItem.OnUsed))] + public class SingleUseKeycardRemainingUses + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder uses = generator.DeclareLocal(typeof(int)); + + Label runLabel = generator.DefineLabel(); + + // after the return + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + 1; + + // get the original jump label and set the jump to our new instructions + object continueLabel = newInstructions[index - 2].operand; + newInstructions[index - 2].operand = runLabel; + + newInstructions.InsertRange(index, new[] + { + // SingleUseKeycard.RemainingUses.TryGetValue(ItemSerial, out int uses); + new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(SingleUseKeycard), nameof(SingleUseKeycard.RemainingUses))).WithLabels(runLabel), + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(SingleUseKeycardItem), nameof(SingleUseKeycardItem.ItemSerial))), + new(OpCodes.Ldloca, uses), + new(OpCodes.Callvirt, Method(typeof(Dictionary), nameof(Dictionary.TryGetValue))), + + // run default behavior (destroying keycard) if no stored uses are found. + new(OpCodes.Brfalse, continueLabel), + + // uses--; + new(OpCodes.Ldloc, uses), + new(OpCodes.Ldc_I4_1), + new(OpCodes.Sub), + new(OpCodes.Stloc, uses), + + // SingleUseKeycard.RemainingUses[ItemSerial] = uses; + new(OpCodes.Call, PropertyGetter(typeof(SingleUseKeycard), nameof(SingleUseKeycard.RemainingUses))), + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(SingleUseKeycardItem), nameof(SingleUseKeycardItem.ItemSerial))), + new(OpCodes.Ldloc, uses), + new(OpCodes.Callvirt, Method(typeof(Dictionary), "set_Item")), + + // if (uses < 1) + // run default behavior (destroying keycard) + new(OpCodes.Ldloc, uses), + new(OpCodes.Ldc_I4_1), + new(OpCodes.Blt, continueLabel), + + // Return; + new(OpCodes.Ret), + }); + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From 6fb368fbd288f1f6a94a7e933c5c35bebb801db9 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:40:50 -0400 Subject: [PATCH 136/224] feat: more CustomRole and CustomAbility register methods that dont rely on attribute (#631) Add Register methods --- .../API/Features/CustomAbility.cs | 31 +++++++++++++++++++ .../API/Features/CustomRole.cs | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomAbility.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomAbility.cs index e39f4bf3d0..1528d08fa3 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomAbility.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomAbility.cs @@ -103,6 +103,37 @@ public static bool TryGet(string name, out CustomAbility? customAbility) return customAbility is not null; } + /// + /// Registers all the 's present in the current assembly. + /// + /// Whether to register by attribute. + /// A of which contains all registered 's. + /// + /// This is just a dumbed down version of for QoL, if you actually use , do not use this overload. + /// + public static IEnumerable RegisterAbilities(bool byAttribute = false) + { + if (byAttribute) + { + return RegisterAbilities(false, null); + } + + List abilities = new(); + + foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) + { + if (type.IsAbstract || !type.IsSubclassOf(typeof(CustomAbility))) + continue; + + CustomAbility ability = (CustomAbility)Activator.CreateInstance(type); + + if (ability.TryRegister()) + abilities.Add(ability); + } + + return abilities; + } + /// /// Registers all the 's present in the current assembly. /// diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 6efb5867c9..f29e2d525c 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -287,6 +287,37 @@ public static bool TryGet(Player player, out IReadOnlyCollection cus return customRoles?.Count > 0; } + /// + /// Registers all the 's present in the current assembly. + /// + /// Whether to register by attribute. + /// A of which contains all registered 's. + /// + /// This is just a dumbed down version of for QoL, if you actually use , do not use this overload. + /// + public static IEnumerable RegisterRoles(bool byAttribute = false) + { + if (byAttribute) + { + return RegisterRoles(false, null); + } + + List roles = new(); + + foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) + { + if (type.IsAbstract || !type.IsSubclassOf(typeof(CustomRole))) + continue; + + CustomRole role = (CustomRole)Activator.CreateInstance(type); + + if (role.TryRegister()) + roles.Add(role); + } + + return roles; + } + /// /// Registers all the 's present in the current assembly. /// From 5890d84945766afa8341e743a075b50b33dfed54 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:42:16 +0200 Subject: [PATCH 137/224] fix: FixNW914DuplicationBug (#643) * FixNW914DuplicationBug * Update FixNW914DuplicationBug.cs --------- Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> --- .../Patches/Fixes/FixNW914DuplicationBug.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs b/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs new file mode 100644 index 0000000000..6b2c60412e --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using HarmonyLib; + using InventorySystem.Items.Pickups; + using Scp914.Processors; + + using static HarmonyLib.AccessTools; + + /// + /// Patches the delegate. + /// Fix items upgraded in SCP-914 being duplicated. + /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/1770). + /// + [HarmonyPatch(typeof(Scp914ItemProcessor), nameof(Scp914ItemProcessor.UpgradeInventoryItem))] + internal class FixNW914DuplicationBug + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldloca_S) + offset; + + newInstructions.InsertRange(index, new[] + { + // itemPickupBase.DestroySelf(); + new CodeInstruction(OpCodes.Ldloc_S, 5), + new CodeInstruction(OpCodes.Callvirt, Method(typeof(ItemPickupBase), nameof(ItemPickupBase.DestroySelf))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From b39f906eed487bf63acbe1ee74bcb2144f2100d8 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 13 Sep 2025 02:57:55 -0400 Subject: [PATCH 138/224] fix: Cassie fixes (#642) Fix cassie! --- .../DamageHandlers/DamageHandlerBase.cs | 2 +- .../DamageHandlers/GenericDamageHandler.cs | 5 +++++ .../Events/Map/AnnouncingTeamEntrance.cs | 18 ++++++++++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs index e88eb3c02f..6e8a47ba5f 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs @@ -289,7 +289,7 @@ public static implicit operator BaseHandler.CassieAnnouncement(CassieAnnouncemen new() { Announcement = cassieAnnouncement.Announcement, - SubtitleParts = cassieAnnouncement.SubtitleParts.ToArray(), + SubtitleParts = cassieAnnouncement.SubtitleParts?.ToArray() ?? Array.Empty(), }; /// diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index 95ebdf36c0..c8c5917241 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -233,6 +233,11 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage /// public PlayerStatsSystem.DamageHandlerBase Base { get; set; } + /// + /// Gets the the base game uses when a player dies. + /// + public override CassieAnnouncement CassieDeathAnnouncement => customCassieAnnouncement; + /// /// Gets or sets the current attacker. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs index a4707c72e6..b1a968cade 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs @@ -28,15 +28,21 @@ namespace Exiled.Events.Patches.Events.Map [HarmonyPatch(typeof(WaveAnnouncementBase), nameof(WaveAnnouncementBase.PlayAnnouncement))] internal static class AnnouncingTeamEntrance { - private static IEnumerable Transpiler(IEnumerable instruction, ILGenerator generator) + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { - List newInstructions = ListPool.Pool.Get(instruction); + List newInstructions = ListPool.Pool.Get(instructions); Label returnLabel = generator.DefineLabel(); - int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldsfld); + // the instruction that sends subtitles is called before stringReturn is created (and thus checked) so we need to move it so that empty (or disallowed) message's subtitles are not sent. + // this removes the Ldarg_0 and the CallVirt + int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(WaveAnnouncementBase), nameof(WaveAnnouncementBase.SendSubtitles)))); + CodeInstruction sendSubtitlesInstruction = newInstructions[index]; + newInstructions.RemoveRange(index - 1, 2); - newInstructions.InsertRange(index, new CodeInstruction[] + index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldsfld); + + newInstructions.InsertRange(index, new[] { // if (stringReturn == "") // return; @@ -44,6 +50,10 @@ private static IEnumerable Transpiler(IEnumerable Date: Sat, 13 Sep 2025 09:06:30 +0200 Subject: [PATCH 139/224] Replace Info to log error and improve information --- EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs index fa087b7ab1..12a4b8701e 100644 --- a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs @@ -32,7 +32,7 @@ internal KeycardPickup(BaseKeycard pickupBase) Base = pickupBase; if (Base is null) { - Log.Info(GetType()); + Log.Error($"[KeycardPickup] Base is null: {GetType()}"); } } From 934022a0c22997281545d3d24aeb5fa8dc42e785 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 13 Sep 2025 09:06:34 +0200 Subject: [PATCH 140/224] 9.9.0-rc.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 83d57709ef..c5f053bbab 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.8.1 + 9.9.0-rc.1 false From a3bf7ad3c3c7a2ac1bdae80371e12b874ea8cea2 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sat, 13 Sep 2025 12:37:39 +0200 Subject: [PATCH 141/224] DecalPoolType --- EXILED/Exiled.API/Enums/BloodType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/BloodType.cs b/EXILED/Exiled.API/Enums/BloodType.cs index 0c77521fd2..369793e2ad 100644 --- a/EXILED/Exiled.API/Enums/BloodType.cs +++ b/EXILED/Exiled.API/Enums/BloodType.cs @@ -14,7 +14,7 @@ namespace Exiled.API.Enums /// /// /// - [Obsolete("This blood decal are outdated now used DealPoolType.Blood", true)] + [Obsolete("This blood decal are outdated now used DecalPoolType.Blood", true)] public enum BloodType { /// From 73e261934f72be0ef5b1d6d750b0baebbdc9b151 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 13 Sep 2025 19:41:34 -0400 Subject: [PATCH 142/224] fix: fix copy paste stuff / pickups with CustomKeycardItem and CustomKeycardPickup (#644) Fixes --- EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs | 2 +- EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs | 2 +- EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs | 2 +- .../Features/Pickups/Keycards/CustomKeycardPickup.cs | 2 ++ .../Features/Pickups/Keycards/ManagementKeycardPickup.cs | 2 +- EXILED/Exiled.API/Features/Pickups/Pickup.cs | 4 ++-- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs index ed7c963a2b..118af77d35 100644 --- a/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycards/MetalKeycard.cs @@ -112,7 +112,7 @@ public string SerialNumber /// The new . public static MetalKeycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear, string serialNumber) { - MetalKeycard keycard = Create(ItemType.KeycardCustomManagement); + MetalKeycard keycard = Create(ItemType.KeycardCustomMetalCase); keycard.KeycardLevels = keycardLevels; keycard.PermissionsColor = permissionsColor; keycard.ItemName = itemName; diff --git a/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs index 5cdf7d842b..9615a416b0 100644 --- a/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycards/Site02Keycard.cs @@ -99,7 +99,7 @@ public byte Wear /// The new . public static Site02Keycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string label, Color labelColor, byte wear) { - Site02Keycard keycard = Create(ItemType.KeycardCustomManagement); + Site02Keycard keycard = Create(ItemType.KeycardCustomSite02); keycard.KeycardLevels = keycardLevels; keycard.PermissionsColor = permissionsColor; keycard.ItemName = itemName; diff --git a/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs index 8775ee0e98..0de8853ab2 100644 --- a/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycards/TaskForceKeycard.cs @@ -87,7 +87,7 @@ public byte Rank /// The new . public static TaskForceKeycard Create(KeycardLevels keycardLevels, Color permissionsColor, string itemName, Color color, string nameTag, string serialNumber, byte rank) { - TaskForceKeycard keycard = Create(ItemType.KeycardCustomManagement); + TaskForceKeycard keycard = Create(ItemType.KeycardCustomTaskForce); keycard.KeycardLevels = keycardLevels; keycard.PermissionsColor = permissionsColor; keycard.ItemName = itemName; diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs index e2c9374ddc..58e24a105e 100644 --- a/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/CustomKeycardPickup.cs @@ -46,6 +46,8 @@ internal CustomKeycardPickup(KeycardPickup pickupBase) internal CustomKeycardPickup(ItemType type) : base(type) { + if (!CustomKeycardItem.DataDict.ContainsKey(Serial)) + CustomKeycardItem.DataDict[Serial] = new KeycardData(); } /// diff --git a/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs index c2adc99aa0..9666c24b21 100644 --- a/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Keycards/ManagementKeycardPickup.cs @@ -60,7 +60,7 @@ public Color LabelColor } /// - /// Creates a . + /// Creates an UnSpawned , to spawn the pickup, call on the returned instance. /// /// The permissions of the keycard. /// The color of the permissions of the keycard. diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index ef1106cf46..3a7011c2ec 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -497,7 +497,7 @@ public static IEnumerable Get(IEnumerable gameObjects) _ => new GrenadePickup(type), }, BaseFirearmPickup => new FirearmPickup(type), - BaseKeycardPickup keycardPickup => type switch + BaseKeycardPickup => type switch { ItemType.KeycardCustomTaskForce => new TaskForceKeycardPickup(type), ItemType.KeycardCustomSite02 => new Site02KeycardPickup(type), @@ -505,7 +505,7 @@ public static IEnumerable Get(IEnumerable gameObjects) ItemType.KeycardCustomMetalCase => new MetalKeycardPickup(type), ItemType.SurfaceAccessPass => new SingleUseKeycardPickup(type), ItemType.KeycardChaosInsurgency => new ChaosKeycardPickup(type), - _ => new KeycardPickup(keycardPickup), + _ => new KeycardPickup(type), }, BaseBodyArmorPickup => new BodyArmorPickup(type), BaseScp330Pickup => new Scp330Pickup(), From c7e27baa639d7386a54140f73a7f31bf3461eb55 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 14 Sep 2025 01:44:45 +0200 Subject: [PATCH 143/224] 9.9.0-rc.2 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index c5f053bbab..e8094004b3 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.9.0-rc.1 + 9.9.0-rc.2 false From 0a897cf4c149817949b6a4512a609185badbfde5 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:30:59 +0200 Subject: [PATCH 144/224] feat: Scp sl update 14.1.4 (#645) * SCP:SL14.1.4 * Fix has been repaired * Add IsSpectatable and SetFakeSpectatable --------- Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> --- .../Exiled.API/Extensions/MirrorExtensions.cs | 17 ++++-- EXILED/Exiled.API/Features/Player.cs | 9 ++++ EXILED/Exiled.API/Features/Ragdoll.cs | 30 +++++------ EXILED/Exiled.API/Features/Window.cs | 17 +++--- .../Player/SpawnedRagdollEventArgs.cs | 6 +-- .../Player/SpawningRagdollEventArgs.cs | 19 ++++--- .../Patches/Fixes/CheaterReportFix.cs | 53 ------------------- .../Patches/Fixes/FixNW914DuplicationBug.cs | 48 ----------------- 8 files changed, 57 insertions(+), 142 deletions(-) delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/CheaterReportFix.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 72f4c05a1b..881e47b588 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -36,6 +36,7 @@ namespace Exiled.API.Extensions using PlayerRoles.FirstPersonControl; using PlayerRoles.PlayableScps.Scp049.Zombies; using PlayerRoles.PlayableScps.Scp1507; + using PlayerRoles.Spectating; using PlayerRoles.Voice; using RelativePositioning; using Respawning; @@ -203,7 +204,7 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { writer.WriteUShort(NetworkMessageId.Id); - new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub).Write(writer); + new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub, null).Write(writer); writer.WriteRelativePosition(new RelativePosition(0, 0, 0, 0, false)); writer.WriteUShort(0); player.Connection.Send(writer); @@ -223,7 +224,7 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), ItemIdentifier.None); - player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub)); + player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub, null)); }); } @@ -248,7 +249,7 @@ public static void PlaceBlood(this Player player, Vector3 position, Vector3 orig using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { writer.WriteUShort(NetworkMessageId.Id); - new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub).Write(writer); + new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub, null).Write(writer); writer.WriteRelativePosition(new RelativePosition(0, 0, 0, 0, false)); writer.WriteUShort(0); player.Connection.Send(writer); @@ -278,7 +279,7 @@ public static void PlaceBlood(this Player player, Vector3 position, Vector3 orig player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), ItemIdentifier.None); - player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub)); + player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub, null)); }); } @@ -462,6 +463,14 @@ public static void SendFakeEffectTo(this Player effectOwner, Player target, Effe }); } + /// + /// Makes a player not spectatable to another player. + /// + /// The player who will become not spectatable. + /// The viewer who will see this change. + /// The faked value. + public static void SetFakeSpectatable(Player target, Player viewer, bool value) => viewer.Connection.Send(new SpectatableVisibilityMessages.SpectatableVisibilityMessage(target.ReferenceHub, value)); + /// /// Makes the server resend a message to all clients updating a keycards details to current values. /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index d81ea63f72..04a392c65c 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1153,6 +1153,15 @@ public bool BadgeHidden /// public bool AgreedToRecording => VoiceChatPrivacySettings.CheckUserFlags(ReferenceHub, VcPrivacyFlags.SettingsSelected | VcPrivacyFlags.AllowRecording | VcPrivacyFlags.AllowMicCapture); + /// + /// Gets or sets a value indicating whether the player can be spectated by a spectator. + /// + public bool IsSpectatable + { + get => SpectatableVisibilityManager.IsHidden(ReferenceHub); + set => SpectatableVisibilityManager.SetHidden(ReferenceHub, value); + } + /// /// Gets a of spectators that are currently spectating this . /// diff --git a/EXILED/Exiled.API/Features/Ragdoll.cs b/EXILED/Exiled.API/Features/Ragdoll.cs index 7f0b05a415..19321ee35d 100644 --- a/EXILED/Exiled.API/Features/Ragdoll.cs +++ b/EXILED/Exiled.API/Features/Ragdoll.cs @@ -12,24 +12,18 @@ namespace Exiled.API.Features using System.Linq; using DeathAnimations; - using Enums; - using Exiled.API.Extensions; using Exiled.API.Interfaces; - using Mirror; - using PlayerRoles; using PlayerRoles.PlayableScps.Scp049.Zombies; using PlayerRoles.Ragdolls; - using PlayerStatsSystem; - + using RelativePositioning; using UnityEngine; using BaseScp3114Ragdoll = PlayerRoles.PlayableScps.Scp3114.Scp3114Ragdoll; - using Object = UnityEngine.Object; /// @@ -101,7 +95,7 @@ public RagdollData NetworkInfo public DamageHandlerBase DamageHandler { get => NetworkInfo.Handler; - set => NetworkInfo = new(NetworkInfo.OwnerHub, value, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, value, NetworkInfo.RoleType, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -145,7 +139,7 @@ public bool CanBeCleanedUp public string Nickname { get => NetworkInfo.Nickname; - set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, value, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, NetworkInfo.Scale, value, NetworkInfo.CreationTime); } /// @@ -154,7 +148,7 @@ public string Nickname public Vector3 Scale { get => NetworkInfo.Scale; - set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, value, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, value, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -168,7 +162,7 @@ public Vector3 Scale public Player Owner { get => Player.Get(NetworkInfo.OwnerHub); - set => NetworkInfo = new(value.ReferenceHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(value.ReferenceHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -180,7 +174,7 @@ public DateTime CreationTime set { float creationTime = (float)(NetworkTime.time - (DateTime.Now - value).TotalSeconds); - NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, creationTime); + NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, NetworkInfo.RoleType, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, NetworkInfo.Scale, NetworkInfo.Nickname, creationTime); } } @@ -190,7 +184,7 @@ public DateTime CreationTime public RoleTypeId Role { get => NetworkInfo.RoleType; - set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, value, NetworkInfo.StartPosition, NetworkInfo.StartRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); + set => NetworkInfo = new(NetworkInfo.OwnerHub, NetworkInfo.Handler, value, NetworkInfo.StartRelativePosition, NetworkInfo.StartRelativeRotation, NetworkInfo.Scale, NetworkInfo.Nickname, NetworkInfo.CreationTime); } /// @@ -334,8 +328,8 @@ public static bool TryCreate(RagdollData networkInfo, out Ragdoll ragdoll) ragdoll = basicRagdoll is BaseScp3114Ragdoll scp3114Ragdoll ? new Scp3114Ragdoll(scp3114Ragdoll) : new Ragdoll(basicRagdoll) { - Position = networkInfo.StartPosition, - Rotation = networkInfo.StartRotation, + Position = networkInfo.StartRelativePosition.Position, + Rotation = RelativePositioning.WaypointBase.GetWorldRotation(networkInfo.StartRelativePosition.WaypointId, networkInfo.StartRelativeRotation), }; return true; @@ -391,7 +385,11 @@ public static Ragdoll CreateAndSpawn(RagdollData networkInfo) /// The optional owner of the ragdoll. /// The ragdoll. public static Ragdoll CreateAndSpawn(RoleTypeId roleType, string name, DamageHandlerBase damageHandler, Vector3 position, Quaternion? rotation = null, Player owner = null) - => CreateAndSpawn(new(owner?.ReferenceHub ?? Server.Host.ReferenceHub, damageHandler, roleType, position, rotation ?? Quaternion.identity, name, NetworkTime.time)); + { + RelativePosition relPos = new(position); + Quaternion relRot = WaypointBase.GetRelativeRotation(relPos.WaypointId, rotation ?? Quaternion.identity); + return CreateAndSpawn(new(owner?.ReferenceHub ?? Server.Host.ReferenceHub, damageHandler, roleType, relPos, relRot, name, NetworkTime.time)); + } /// /// Creates and spawns a new ragdoll. diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index 892733246b..cc99dbcb61 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -62,7 +62,7 @@ internal Window(BreakableWindow window, Room room) /// /// Gets the window's . /// - public Transform Transform => Base._transform; + public Transform Transform => Base.transform; /// /// Gets the the window is in. @@ -91,15 +91,15 @@ public Vector3 Position /// /// Gets a value indicating whether this window is breakable. /// - public bool IsBreakable => !Base.isBroken; + public bool IsBreakable => !Base.IsBroken; /// /// Gets or sets a value indicating whether this window is broken. /// public bool IsBroken { - get => Base.isBroken; - set => Base.isBroken = value; + get => Base.IsBroken; + set => Base.IsBroken = value; } /// @@ -107,8 +107,8 @@ public bool IsBroken /// public float Health { - get => Base.health; - set => Base.health = value; + get => Base.Health; + set => Base.Health = value; } /// @@ -132,10 +132,11 @@ public bool DisableScpDamage /// /// Gets or sets a value indicating whether this window is broken. /// + [Obsolete("You should use IsBroken Propperty now", true)] public bool SyncStatus { - get => Base.prevStatus; - set => Base.prevStatus = value; + get => Base._prevStatus; + set => Base._prevStatus = value; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/SpawnedRagdollEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SpawnedRagdollEventArgs.cs index b2020678c9..a500495017 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SpawnedRagdollEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SpawnedRagdollEventArgs.cs @@ -14,7 +14,7 @@ namespace Exiled.Events.EventArgs.Player using PlayerRoles; using PlayerRoles.Ragdolls; using PlayerStatsSystem; - + using RelativePositioning; using UnityEngine; /// @@ -48,12 +48,12 @@ public SpawnedRagdollEventArgs(Player player, Ragdoll ragdoll, RagdollData info, /// /// Gets the ragdoll's position. /// - public Vector3 Position => Info.StartPosition; + public Vector3 Position => Info.StartRelativePosition.Position; /// /// Gets the ragdoll's rotation. /// - public Quaternion Rotation => Info.StartRotation; + public Quaternion Rotation => WaypointBase.GetWorldRotation(Info.StartRelativePosition.WaypointId, Info.StartRelativeRotation); /// /// Gets the ragdoll's . diff --git a/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs index 9ec7423835..9212024a25 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SpawningRagdollEventArgs.cs @@ -13,9 +13,8 @@ namespace Exiled.Events.EventArgs.Player using PlayerRoles; using PlayerRoles.Ragdolls; using PlayerStatsSystem; - + using RelativePositioning; using UnityEngine; - using YamlDotNet.Core.Tokens; /// /// Contains all information before spawning a player ragdoll. @@ -44,8 +43,8 @@ public SpawningRagdollEventArgs(RagdollData info, bool isAllowed = true) /// public Vector3 Position { - get => Info.StartPosition; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, value, Rotation, Scale, Nickname, CreationTime); + get => Info.StartRelativePosition.Position; + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, new(value), Info.StartRelativeRotation, Scale, Nickname, CreationTime); } /// @@ -53,8 +52,8 @@ public Vector3 Position /// public Quaternion Rotation { - get => Info.StartRotation; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, value, Scale, Nickname, CreationTime); + get => WaypointBase.GetWorldRotation(Info.StartRelativePosition.WaypointId, Info.StartRelativeRotation); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Info.StartRelativePosition, WaypointBase.GetWorldRotation(Info.StartRelativePosition.WaypointId, value), Scale, Nickname, CreationTime); } /// @@ -63,7 +62,7 @@ public Quaternion Rotation public Vector3 Scale { get => Info.Scale; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, Rotation, Vector3.Scale(value, RagdollManager.GetDefaultScale(Role)), Nickname, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Info.StartRelativePosition, Info.StartRelativeRotation, Vector3.Scale(value, RagdollManager.GetDefaultScale(Role)), Nickname, CreationTime); } /// @@ -77,7 +76,7 @@ public Vector3 Scale public RoleTypeId Role { get => Info.RoleType; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, value, Position, Rotation, Scale, Nickname, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, value, Info.StartRelativePosition, Info.StartRelativeRotation, Scale, Nickname, CreationTime); } /// @@ -91,7 +90,7 @@ public RoleTypeId Role public string Nickname { get => Info.Nickname; - set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Position, Rotation, Scale, value, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, DamageHandlerBase, Role, Info.StartRelativePosition, Info.StartRelativeRotation, Scale, value, CreationTime); } /// @@ -105,7 +104,7 @@ public string Nickname public DamageHandlerBase DamageHandlerBase { get => Info.Handler; - set => Info = new RagdollData(Player.ReferenceHub, value, Role, Position, Rotation, Scale, Nickname, CreationTime); + set => Info = new RagdollData(Player.ReferenceHub, value, Role, Info.StartRelativePosition, Info.StartRelativeRotation, Scale, Nickname, CreationTime); } /// diff --git a/EXILED/Exiled.Events/Patches/Fixes/CheaterReportFix.cs b/EXILED/Exiled.Events/Patches/Fixes/CheaterReportFix.cs deleted file mode 100644 index b88fe8b11d..0000000000 --- a/EXILED/Exiled.Events/Patches/Fixes/CheaterReportFix.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using Exiled.API.Features; - using Exiled.API.Features.Pools; - - using HarmonyLib; - - /// - /// Patches . - /// Fixes method not working. - /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/1814). - /// - [HarmonyPatch(typeof(CheaterReport), nameof(CheaterReport.SubmitReport))] - public class CheaterReportFix - { - private static IEnumerable Transpiler(IEnumerable instructions) - { - List newInstructions = ListPool.Pool.Get(instructions); - - CodeInstruction instruction = newInstructions.Find(instruction => instruction.opcode == OpCodes.Ldstr); - - // will notify devs of fix when NW fixes themselves, rather than another silent error - if (instruction.operand is not "payload_json=") - { - Log.Error($"{typeof(CheaterReportFix).FullName} failed to fix {typeof(CheaterReport).FullName}.{nameof(CheaterReport.SubmitReport)}, please verify this fix is still required and fix it if so."); - - for (int index = 0; index < newInstructions.Count; index++) - yield return newInstructions[index]; - - ListPool.Pool.Return(newInstructions); - yield break; - } - - // easiest solution with minimal changes, but NW fixing it will make us cause issue. - instruction.operand = string.Empty; - - for (int index = 0; index < newInstructions.Count; index++) - yield return newInstructions[index]; - - ListPool.Pool.Return(newInstructions); - } - } -} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs b/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs deleted file mode 100644 index 6b2c60412e..0000000000 --- a/EXILED/Exiled.Events/Patches/Fixes/FixNW914DuplicationBug.cs +++ /dev/null @@ -1,48 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - using HarmonyLib; - using InventorySystem.Items.Pickups; - using Scp914.Processors; - - using static HarmonyLib.AccessTools; - - /// - /// Patches the delegate. - /// Fix items upgraded in SCP-914 being duplicated. - /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/1770). - /// - [HarmonyPatch(typeof(Scp914ItemProcessor), nameof(Scp914ItemProcessor.UpgradeInventoryItem))] - internal class FixNW914DuplicationBug - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - int offset = 1; - int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldloca_S) + offset; - - newInstructions.InsertRange(index, new[] - { - // itemPickupBase.DestroySelf(); - new CodeInstruction(OpCodes.Ldloc_S, 5), - new CodeInstruction(OpCodes.Callvirt, Method(typeof(ItemPickupBase), nameof(ItemPickupBase.DestroySelf))), - }); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; - - ListPool.Pool.Return(newInstructions); - } - } -} \ No newline at end of file From 9f89eb802438b17a67c07b7379ca2587a734e397 Mon Sep 17 00:00:00 2001 From: Yamato Date: Sun, 14 Sep 2025 18:59:06 +0200 Subject: [PATCH 145/224] 9.9.1-rc.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index e8094004b3..23caaa7421 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.9.0-rc.2 + 9.9.1-rc.1 false From 8cfe458101b9b01823aaf2419f8c65e126f7c766 Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 16 Sep 2025 09:56:35 +0200 Subject: [PATCH 146/224] build T4 file on SCP:SL 14.1.4 --- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- .../SCPSLRessources/NW_Documentation.md | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 676ba8bad3..36c845a38d 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, 3); + public static readonly Version RequiredSCPSLVersion = new(14, 1, 0, 4); } } \ No newline at end of file diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 1f5d7669fe..748a73f091 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.1) +Last Update (14.1.0.4) ### Index @@ -28,6 +28,7 @@ Last Update (14.1.0.1) - [ActionName](#actionname) - [Activity](#activity) - [AdminFlags](#adminflags) +- [AnimationQuality](#animationquality) - [AnimItemLayer3p](#animitemlayer3p) - [AnimState3p](#animstate3p) - [AttachmentDescriptiveAdvantages](#attachmentdescriptiveadvantages) @@ -491,6 +492,20 @@ Last Update (14.1.0.1) +### AnimationQuality + +
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.CullingSubcontroller+AnimationQuality + +``` + [0] = VeryLow + [1] = Low + [2] = Medium + [3] = High + [4] = Ultra +``` + +
+ ### AnimItemLayer3p
InventorySystem.Items.Thirdperson.AnimItemLayer3p @@ -3544,6 +3559,7 @@ Last Update (14.1.0.1) [7] = BulletDecalsEnabled [8] = BloodDecalsLimit [9] = BulletDecalsLimits + [10] = AnimationQuality ```
@@ -3669,6 +3685,7 @@ Last Update (14.1.0.1) [268435456] = FriendlyFireDetectorTempDisable [536870912] = ServerLogLiveFeed [1073741824] = ExecuteAs + [2147483648] = Vanish ``` @@ -3861,7 +3878,6 @@ Last Update (14.1.0.1) [1] = ConfirmThrowWeak [2] = ConfirmThrowFullForce [3] = CancelThrow - [4] = ForceCancel ``` @@ -5648,7 +5664,7 @@ Last Update (14.1.0.1)
Damage Handlers -```md title="Latest Updated: 14.1.0.1" +```md title="Latest Updated: 14.1.0.4" All available DamageHandlers + Symbol ':' literally means "inherits from" From 4906105b6131e182d692b77674ce90d66141d608 Mon Sep 17 00:00:00 2001 From: Yamato Date: Tue, 16 Sep 2025 21:04:32 +0200 Subject: [PATCH 147/224] 9.9.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 23caaa7421..7ac4c94066 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.9.1-rc.1 + 9.9.1 false From 483e048b321c38bf44a3806ae57503eab5f6bfbc Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 18 Sep 2025 10:46:41 +0200 Subject: [PATCH 148/224] fix: IsSpectatable being reverted --- EXILED/Exiled.API/Features/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 04a392c65c..82f381031d 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1158,8 +1158,8 @@ public bool BadgeHidden ///
public bool IsSpectatable { - get => SpectatableVisibilityManager.IsHidden(ReferenceHub); - set => SpectatableVisibilityManager.SetHidden(ReferenceHub, value); + get => !SpectatableVisibilityManager.IsHidden(ReferenceHub); + set => SpectatableVisibilityManager.SetHidden(ReferenceHub, !value); } /// From 69f9141040efc1028a68d57cc0501a06a7735f10 Mon Sep 17 00:00:00 2001 From: Yamato Date: Thu, 18 Sep 2025 13:13:22 +0200 Subject: [PATCH 149/224] v9.9.2 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 7ac4c94066..589a0072b1 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.9.1 + 9.9.2 false From 0d5d3a7691b48c134f31fada4048c312aa31437c Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:14:16 +0200 Subject: [PATCH 150/224] feat: Add Intercom TrySetOverride(Player, bool) and HasOverride(Player) (#646) * Add TrySetOverride(Player, bool) and HasOverride(Player) * Fix doc --- EXILED/Exiled.API/Features/Intercom.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/EXILED/Exiled.API/Features/Intercom.cs b/EXILED/Exiled.API/Features/Intercom.cs index dde962f919..72bd0c254d 100644 --- a/EXILED/Exiled.API/Features/Intercom.cs +++ b/EXILED/Exiled.API/Features/Intercom.cs @@ -88,6 +88,21 @@ public static float SpeechRemainingTime /// Sets a value indicating whether the sound is the intercom's start speaking sound. public static void PlaySound(bool isStarting) => GameIntercom._singleton.RpcPlayClip(isStarting); + /// + /// Modifies whether the player is overriding the intercom to speak globally. + /// + /// The whose intercom override state will be changed. + /// Indicates whether the player should be given the global intercom override. + /// if the player was successfully added or removed from the override list; otherwise, . + public static bool TrySetOverride(Player player, bool newState) => GameIntercom.TrySetOverride(player?.ReferenceHub, newState); + + /// + /// Checks whether the player is currently overriding the intercom to speak globally. + /// + /// The to check for a global intercom override. + /// if the player has global intercom override; otherwise, . + public static bool HasOverride(Player player) => GameIntercom.HasOverride(player?.ReferenceHub); + /// /// Reset the intercom's cooldown. /// From 8a21ec7818e0ee135a4500cefc6caf5c1888edd1 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:22:50 +0200 Subject: [PATCH 151/224] fix: TriggeringTesla for LabAPI (#649) Fix TriggeringTesla --- .../Patches/Events/Player/TriggeringTesla.cs | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/TriggeringTesla.cs b/EXILED/Exiled.Events/Patches/Events/Player/TriggeringTesla.cs index 4034169a6a..d14539587e 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/TriggeringTesla.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/TriggeringTesla.cs @@ -15,8 +15,9 @@ namespace Exiled.Events.Patches.Events.Player using Exiled.API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Player; - using HarmonyLib; + using LabApi.Events.Arguments.PlayerEvents; + using LabApi.Events.Handlers; using static HarmonyLib.AccessTools; @@ -55,8 +56,14 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - private static void TriggeringTeslaEvent(BaseTeslaGate baseTeslaGate, ref bool inIdleRange, ref bool isTriggerable) + private static void TriggeringTeslaEvent(BaseTeslaGate baseTeslaGate, ref bool inIdleRange, ref bool isTriggerable, ref ReferenceHub referenceHub, ref ReferenceHub referenceHub2) { TeslaGate teslaGate = TeslaGate.Get(baseTeslaGate); @@ -88,11 +95,35 @@ private static void TriggeringTeslaEvent(BaseTeslaGate baseTeslaGate, ref bool i if (!ev.IsAllowed) continue; - if (ev.IsTriggerable && !isTriggerable) + if (ev.IsTriggerable && !isTriggerable && !teslaGate.IsShocking) + { isTriggerable = ev.IsTriggerable; + PlayerTriggeringTeslaEventArgs playerTriggeringTeslaEventArgs = new(player.ReferenceHub, teslaGate.Base); + PlayerEvents.OnTriggeringTesla(playerTriggeringTeslaEventArgs); + if (!playerTriggeringTeslaEventArgs.IsAllowed) + { + isTriggerable = false; + } + else + { + referenceHub2 = player.ReferenceHub; + } + } if (ev.IsInIdleRange && !inIdleRange) + { inIdleRange = ev.IsInIdleRange; + PlayerIdlingTeslaEventArgs playerIdlingTeslaEventArgs = new(player.ReferenceHub, teslaGate.Base); + PlayerEvents.OnIdlingTesla(playerIdlingTeslaEventArgs); + if (!playerIdlingTeslaEventArgs.IsAllowed) + { + inIdleRange = false; + } + else + { + referenceHub = player.ReferenceHub; + } + } } } } From 0aacbe1b1c184037b2b4bb1e3bccdb2af260ec44 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 24 Sep 2025 04:17:08 -0400 Subject: [PATCH 152/224] fix: CustomKeycards (CI) not working (#650) Fix --- .../API/Features/CustomKeycard.cs | 91 +++++++++++++------ 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs index c7a225a897..1777953129 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomKeycard.cs @@ -15,11 +15,12 @@ namespace Exiled.CustomItems.API.Features using Exiled.API.Features; using Exiled.API.Features.Doors; using Exiled.API.Features.Items; + using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Lockers; using Exiled.API.Features.Pickups; + using Exiled.API.Interfaces.Keycards; using Exiled.Events.EventArgs.Item; using Exiled.Events.EventArgs.Player; - using Interactables.Interobjects.DoorUtils; using InventorySystem.Items.Keycards; using UnityEngine; @@ -72,6 +73,30 @@ public override ItemType Type /// public virtual Color32? KeycardPermissionsColor { get; set; } + /// + /// Gets or sets the wear of a keycard. + /// + /// + /// Only works on CustomSite02 keycards with values 0-4 and CustomMetalCase keycards with values 0-5. + /// + public virtual byte Wear { get; set; } = byte.MaxValue; + + /// + /// Gets or sets the serial number of a keycard. + /// + /// + /// Only works on CustomMetalCase and CustomTaskForce keycards. Max length is 12. Non-numerical characters will be replaced with '-'. + /// + public virtual string SerialNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the rank of a keycard. + /// + /// + /// Only works on CustomTaskForce keycards with values 0-3. Value runs in reverse (3 is rank 1, 2 -> 2, 1 -> 3, 0 -> 0). + /// + public virtual byte Rank { get; set; } = byte.MaxValue; + /// public override void Give(Player player, Item item, bool displayMessage = true) { @@ -95,45 +120,55 @@ public override void Give(Player player, Item item, bool displayMessage = true) /// Item instance. protected virtual void SetupKeycard(Keycard keycard) { - if (!keycard.Base.Customizable) - return; + if (keycard is CustomKeycardItem customKeycard) + { + customKeycard.Permissions = Permissions; - DetailBase[] details = keycard.Base.Details; + if (KeycardPermissionsColor.HasValue) + customKeycard.PermissionsColor = KeycardPermissionsColor.Value; - NametagDetail? nameDetail = details.OfType().FirstOrDefault(); + if (TintColor.HasValue) + customKeycard.Color = TintColor.Value; - if (nameDetail != null && !string.IsNullOrEmpty(KeycardName)) - NametagDetail._customNametag = KeycardName; + if (!string.IsNullOrEmpty(Name)) + customKeycard.ItemName = Name; - CustomItemNameDetail? raNameDetail = details.OfType().FirstOrDefault(); + if (!string.IsNullOrEmpty(KeycardName) && customKeycard is INameTagKeycard nametag) + nametag.NameTag = KeycardName; - if (raNameDetail != null) - raNameDetail.Name = Name; + if (customKeycard is ILabelKeycard label) + { + if (!string.IsNullOrEmpty(KeycardLabel)) + label.Label = KeycardLabel; + if (KeycardLabelColor.HasValue) + label.LabelColor = KeycardLabelColor.Value; + } - CustomLabelDetail? labelDetail = details.OfType().FirstOrDefault(); + if (customKeycard is IWearKeycard wear) + wear.Wear = Wear; - if (labelDetail != null) - { - if (!string.IsNullOrEmpty(KeycardLabel)) - CustomLabelDetail._customText = KeycardLabel; + if (customKeycard is ISerialNumberKeycard serialNumber) + serialNumber.SerialNumber = SerialNumber; - if (KeycardLabelColor.HasValue) - CustomLabelDetail._customColor = KeycardLabelColor.Value; + if (customKeycard is IRankKeycard rank) + rank.Rank = Rank; } - - CustomPermsDetail? permsDetail = details.OfType().FirstOrDefault(); - - if (permsDetail != null) + else if (keycard.Base.Customizable) { - CustomPermsDetail._customLevels = new((DoorPermissionFlags)Permissions); - CustomPermsDetail._customColor = KeycardPermissionsColor; - } + // Some keycards have customizable name tags but nothing else. This should handle those. + DetailBase[] details = keycard.Base.Details; - CustomTintDetail? tintDetail = details.OfType().FirstOrDefault(); + NametagDetail? nametag = details.OfType().FirstOrDefault(); - if (tintDetail != null && TintColor.HasValue) - { - CustomTintDetail._customColor = TintColor.Value; + if (nametag != null) + { + NametagDetail._customNametag = KeycardName; + + if (KeycardDetailSynchronizer.Database.Remove(keycard.Serial)) + { + KeycardDetailSynchronizer.ServerProcessItem(keycard.Base); + } + } } } From 583874f6e7ff7bedbadcef74ddaf3597f1c28572 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:15:16 +0200 Subject: [PATCH 153/224] This has been fixed --- EXILED/Exiled.API/Features/Toys/AdminToy.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/EXILED/Exiled.API/Features/Toys/AdminToy.cs b/EXILED/Exiled.API/Features/Toys/AdminToy.cs index 8e83170121..c93cee110e 100644 --- a/EXILED/Exiled.API/Features/Toys/AdminToy.cs +++ b/EXILED/Exiled.API/Features/Toys/AdminToy.cs @@ -108,13 +108,6 @@ public Vector3 Scale get => AdminToyBase.transform.localScale; set { - // TODO: Remove this part of code when NW will have fix the issue - if (this is Waypoint) - { - if (value.x != value.y || value.y != value.z || value.sqrMagnitude >= Vector3.one.sqrMagnitude) - Log.Warn("NW WaypointToy have error when Scale is bigger than one or than x y z are not the same"); - } - AdminToyBase.transform.localScale = value; AdminToyBase.NetworkScale = value; } From 01d5aa67f0789bc56c206dee46812c48d0f3ad37 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:16:27 +0200 Subject: [PATCH 154/224] Speaker::Volume doc --- EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs index 859a8f2055..568aed81ee 100644 --- a/EXILED/Exiled.API/Features/Toys/Speaker.cs +++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs @@ -43,7 +43,7 @@ internal Speaker(SpeakerToy speakerToy) ///
/// /// A representing the volume level of the audio source, - /// where 0.0 is silent and 1.0 is full volume. + /// where 0.0 is silent and 1.0 is full volume if it's more it's will amplify it. /// public float Volume { From eb104bd3bc00ce47899ae29c06f10d631a6a5868 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:50:14 +0200 Subject: [PATCH 155/224] RoomType::Hcz127 doc fix --- EXILED/Exiled.API/Enums/RoomType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/RoomType.cs b/EXILED/Exiled.API/Enums/RoomType.cs index 6e236c25d1..a140d2e609 100644 --- a/EXILED/Exiled.API/Enums/RoomType.cs +++ b/EXILED/Exiled.API/Enums/RoomType.cs @@ -330,7 +330,7 @@ public enum RoomType EzSmallrooms, /// - /// Heavy Containment Zone's SCP-330 room. + /// Heavy Containment Zone's SCP-127 room. /// Hcz127, From 5f3c7c2124ac1734c6d58c756d155d64371feb13 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:33:46 +0200 Subject: [PATCH 156/224] Fix Speaker::Volume --- EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs index 859a8f2055..a7b177e6b4 100644 --- a/EXILED/Exiled.API/Features/Toys/Speaker.cs +++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs @@ -48,7 +48,7 @@ internal Speaker(SpeakerToy speakerToy) public float Volume { get => Base.NetworkVolume; - set => Base.NetworkVolume = Mathf.Clamp01(value); + set => Base.NetworkVolume = value; } /// From 8d9f0f4116ae321614887e3f5c01e28aceaf3881 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:03:49 +0200 Subject: [PATCH 157/224] not used anymore --- .../Patches/Events/Warhead/DeadmanSwitchStart.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Warhead/DeadmanSwitchStart.cs b/EXILED/Exiled.Events/Patches/Events/Warhead/DeadmanSwitchStart.cs index 9d88dd0605..e5bb5e1aae 100644 --- a/EXILED/Exiled.Events/Patches/Events/Warhead/DeadmanSwitchStart.cs +++ b/EXILED/Exiled.Events/Patches/Events/Warhead/DeadmanSwitchStart.cs @@ -20,7 +20,7 @@ namespace Exiled.Events.Patches.Events.Warhead /// /// Patches - /// to add and events. + /// to add events. /// [EventPatch(typeof(Warhead), nameof(Warhead.DeadmanSwitchInitiating))] [HarmonyPatch(typeof(DeadmanSwitch), nameof(DeadmanSwitch.InitiateProtocol))] @@ -35,11 +35,6 @@ private static IEnumerable Transpiler(IEnumerable Date: Mon, 29 Sep 2025 19:46:18 +0200 Subject: [PATCH 158/224] Fix Rotation --- EXILED/Exiled.API/Features/Player.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 82f381031d..29f6d05028 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -522,8 +522,15 @@ public RelativePosition RelativePosition /// Returns the direction the player is looking at. public Quaternion Rotation { - get => Transform.rotation; - set => ReferenceHub.TryOverrideRotation(value.eulerAngles); + get => CameraTransform.rotation; + set + { + Vector2 rotation = value.eulerAngles; + rotation.x = Mathf.Repeat(rotation.x + 180f, 360f) - 180f; // X Rotation is limited to [-88, 88] degrees, and just clamps values like 400 even though they are in range + rotation.x *= -1; // X Rotation is inverted in class FpcMouseLook + rotation.y = Mathf.Repeat(rotation.y, 360f); // This is necessary because rotation is clamped in FpcMouseLook + ReferenceHub.TryOverrideRotation(rotation); + } } /// From ea9f87b986470448d9aa8d238d18791f6b2ff85a Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Tue, 7 Oct 2025 04:40:08 -0400 Subject: [PATCH 159/224] fix: Fix SendFakeSyncVar throwing client-side errors and not doing sub-toy syncvars. Fix misc toys. Fix some docs. (#654) --- .../Exiled.API/Extensions/MirrorExtensions.cs | 27 +++++++++++++++++++ EXILED/Exiled.API/Features/Camera.cs | 11 +++++++- EXILED/Exiled.API/Features/Toys/CameraToy.cs | 2 +- EXILED/Exiled.API/Features/Toys/Text.cs | 6 ++--- EXILED/Exiled.API/Features/Toys/Waypoint.cs | 21 ++++++++++----- 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 881e47b588..90982b9220 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Extensions using System.Reflection.Emit; using System.Text; + using AdminToys; using AudioPooling; using CustomPlayerEffects; using Exiled.API.Enums; @@ -56,6 +57,7 @@ public static class MirrorExtensions private static readonly ReadOnlyDictionary ReadOnlyRpcFullNamesValue = new(RpcFullNamesValue); private static MethodInfo setDirtyBitsMethodInfoValue; private static MethodInfo sendSpawnMessageMethodInfoValue; + private static string[] adminToyBaseSyncVarsValue; /// /// Gets corresponding to . @@ -157,6 +159,11 @@ public static ReadOnlyDictionary RpcFullNames /// public static MethodInfo SendSpawnMessageMethodInfo => sendSpawnMessageMethodInfoValue ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static); + /// + /// Gets all sync var names. + /// + public static string[] AdminToyBaseSyncVars => adminToyBaseSyncVarsValue ??= typeof(AdminToyBase).GetProperties().Where(property => property.Name.Contains("Network")).Select(property => property.Name).ToArray(); + /// /// Plays a beep sound that only the target can hear. /// @@ -677,8 +684,25 @@ public static void SendFakeSyncVar(this Player target, NetworkIdentity behavi NetworkWriterPool.Return(writer2); void CustomSyncVarGenerator(NetworkWriter targetWriter) { + bool isAdminToy = targetType.BaseType == typeof(AdminToyBase); + + // all admin toys have toy-specific sync vars, so we need to write correct dirty bits for both the toys DeserializeSyncVars, but also AdminToyBase.DeserializeSyncVars in correct order + bool isBase = AdminToyBaseSyncVars.Contains(propertyName); + + if (isAdminToy && !isBase) + { + // no base sync var changes + targetWriter.WriteULong(0); + } + targetWriter.WriteULong(SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"]); WriterExtensions[typeof(T)]?.Invoke(null, new object[2] { targetWriter, value }); + + if (isAdminToy && isBase) + { + // no sub-toy sync var changes + targetWriter.WriteULong(0); + } } } @@ -796,6 +820,9 @@ private static void MakeCustomSyncWriter(NetworkIdentity behaviorOwner, Type tar } } + if (!behaviour) + throw new InvalidOperationException($"Failed to find a valid NetworkBehaviour for NetworkIdentity: [{behaviorOwner.name}], type: [{targetType.FullName}]. Please verify you are using the correct Type in SendFakeSync Var/Object methods."); + // Write target NetworkBehavior's dirty Compression.CompressVarUInt(owner, value); diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index e38b7abb35..1b03ab2a4d 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -160,7 +160,16 @@ internal Camera(Scp079Camera camera079) Camera079ToCamera.Add(camera079, this); Type = GetCameraType(); if (Base != null && Type is CameraType.Unknown) - Log.Error($"[CameraType] Room: {Room?.Type ?? RoomType.Unknown} Name:{Name}"); + { + if (Base.IsToy) + { + // If NW gives a way to tell CameraType by game object, set Type here, otherwise we cant tell what CameraType is. + } + else + { + Log.Error($"[Camera] Add CameraType entry for: [Room: {Room?.Type ?? RoomType.Unknown} Name: {Name}]"); + } + } } /// diff --git a/EXILED/Exiled.API/Features/Toys/CameraToy.cs b/EXILED/Exiled.API/Features/Toys/CameraToy.cs index 518c4d789a..0e2b856b65 100644 --- a/EXILED/Exiled.API/Features/Toys/CameraToy.cs +++ b/EXILED/Exiled.API/Features/Toys/CameraToy.cs @@ -21,7 +21,7 @@ namespace Exiled.API.Features.Toys /// /// A wrapper class for . /// - internal class CameraToy : AdminToy, IWrapper + public class CameraToy : AdminToy, IWrapper { /// /// Initializes a new instance of the class. diff --git a/EXILED/Exiled.API/Features/Toys/Text.cs b/EXILED/Exiled.API/Features/Toys/Text.cs index 7c3317cd22..fe2543a42f 100644 --- a/EXILED/Exiled.API/Features/Toys/Text.cs +++ b/EXILED/Exiled.API/Features/Toys/Text.cs @@ -20,9 +20,9 @@ public class Text : AdminToy, IWrapper /// /// Initializes a new instance of the class. /// - /// The of the toy. - internal Text(TextToy speakerToy) - : base(speakerToy, AdminToyType.TextToy) => Base = speakerToy; + /// The of the toy. + internal Text(TextToy textToy) + : base(textToy, AdminToyType.TextToy) => Base = textToy; /// /// Gets the prefab. diff --git a/EXILED/Exiled.API/Features/Toys/Waypoint.cs b/EXILED/Exiled.API/Features/Toys/Waypoint.cs index 13dce85273..5f65aec1f2 100644 --- a/EXILED/Exiled.API/Features/Toys/Waypoint.cs +++ b/EXILED/Exiled.API/Features/Toys/Waypoint.cs @@ -20,9 +20,9 @@ public class Waypoint : AdminToy, IWrapper /// /// Initializes a new instance of the class. /// - /// The of the toy. - internal Waypoint(WaypointToy speakerToy) - : base(speakerToy, AdminToyType.WaypointToy) => Base = speakerToy; + /// The of the toy. + internal Waypoint(WaypointToy waypointToy) + : base(waypointToy, AdminToyType.WaypointToy) => Base = waypointToy; /// /// Gets the prefab. @@ -39,8 +39,8 @@ internal Waypoint(WaypointToy speakerToy) /// public float Priority { - get => Base.Priority; - set => Base.Priority = value; + get => Base.NetworkPriority; + set => Base.NetworkPriority = value; } /// @@ -53,7 +53,16 @@ public bool VisualizeBounds } /// - /// Gets the id of the Waypoint use for . + /// Gets or sets the bounds this waypoint encapsulates. + /// + public Bounds Bounds + { + get => new(Position, Base.NetworkBoundsSize); + set => Base.NetworkBoundsSize = value.size; + } + + /// + /// Gets the id of the Waypoint used for . /// public byte WaypointId => Base._waypointId; } From 0d018f78234fe1a434d121124826ebb4edf473e4 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:18:04 +0200 Subject: [PATCH 160/224] fix: Support nullable vector & color (#651) --- .../CustomConverters/ColorConverter.cs | 34 ++++++++++++++-- .../CustomConverters/VectorsConverter.cs | 39 ++++++++++++++++--- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/ColorConverter.cs b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/ColorConverter.cs index 6bb6768cab..fb29d82930 100644 --- a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/ColorConverter.cs +++ b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/ColorConverter.cs @@ -12,6 +12,7 @@ namespace Exiled.Loader.Features.Configs.CustomConverters using System.Globalization; using System.IO; + using Exiled.API.Features; using Exiled.API.Features.Pools; using UnityEngine; @@ -21,18 +22,37 @@ namespace Exiled.Loader.Features.Configs.CustomConverters using YamlDotNet.Serialization; /// - /// Converts to Yaml configs and vice versa. + /// Converts (including nullable) to Yaml configs and vice versa. /// public sealed class ColorConverter : IYamlTypeConverter { /// - public bool Accepts(Type type) => type == typeof(Color); + public bool Accepts(Type type) + { + Type baseType = Nullable.GetUnderlyingType(type) ?? type; + return baseType == typeof(Color); + } /// public object ReadYaml(IParser parser, Type type) { + Type baseType = Nullable.GetUnderlyingType(type) ?? type; + + if (parser.TryConsume(out Scalar scalar)) + { + if (string.IsNullOrEmpty(scalar.Value) || scalar.Value.Equals("null", StringComparison.OrdinalIgnoreCase)) + { + if (Nullable.GetUnderlyingType(type) != null) + return null; + + Log.Error($"Cannot assign null to non-nullable type {baseType.FullName}."); + } + + Log.Error($"Expected mapping, but got scalar: {scalar.Value}"); + } + if (!parser.TryConsume(out _)) - throw new InvalidDataException($"Cannot deserialize object of type {type.FullName}"); + Log.Error($"Cannot deserialize object of type {type.FullName}."); List coordinates = ListPool.Pool.Get(4); int i = 0; @@ -45,7 +65,7 @@ public object ReadYaml(IParser parser, Type type) continue; } - if (!parser.TryConsume(out Scalar scalar) || !float.TryParse(scalar.Value, NumberStyles.Float, CultureInfo.GetCultureInfo("en-US"), out float coordinate)) + if (!parser.TryConsume(out Scalar coordScalar) || !float.TryParse(coordScalar.Value, NumberStyles.Float, CultureInfo.GetCultureInfo("en-US"), out float coordinate)) { ListPool.Pool.Return(coordinates); throw new InvalidDataException("Invalid float value."); @@ -64,6 +84,12 @@ public object ReadYaml(IParser parser, Type type) /// public void WriteYaml(IEmitter emitter, object value, Type type) { + if (value is null) + { + emitter.Emit(new Scalar("null")); + return; + } + Dictionary coordinates = DictionaryPool.Pool.Get(); if (value is Color color) diff --git a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/VectorsConverter.cs b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/VectorsConverter.cs index 944e2f2784..63f413247d 100644 --- a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/VectorsConverter.cs +++ b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/VectorsConverter.cs @@ -12,6 +12,7 @@ namespace Exiled.Loader.Features.Configs.CustomConverters using System.Globalization; using System.IO; + using Exiled.API.Features; using Exiled.API.Features.Pools; using UnityEngine; @@ -21,18 +22,37 @@ namespace Exiled.Loader.Features.Configs.CustomConverters using YamlDotNet.Serialization; /// - /// Converts a Vector2, Vector3 or Vector4 to Yaml configs and vice versa. + /// Converts a Vector2, Vector3 or Vector4 (including nullable) to Yaml configs and vice versa. /// public sealed class VectorsConverter : IYamlTypeConverter { /// - public bool Accepts(Type type) => type == typeof(Vector2) || type == typeof(Vector3) || type == typeof(Vector4); + public bool Accepts(Type type) + { + Type baseType = Nullable.GetUnderlyingType(type) ?? type; + return baseType == typeof(Vector2) || baseType == typeof(Vector3) || baseType == typeof(Vector4); + } /// public object ReadYaml(IParser parser, Type type) { + Type baseType = Nullable.GetUnderlyingType(type) ?? type; + + if (parser.TryConsume(out Scalar scalar)) + { + if (string.IsNullOrEmpty(scalar.Value) || scalar.Value.Equals("null", StringComparison.OrdinalIgnoreCase)) + { + if (Nullable.GetUnderlyingType(type) != null) + return null; + + Log.Error($"Cannot assign null to non-nullable type {baseType.FullName}."); + } + + Log.Error($"Expected mapping, but got scalar: {scalar.Value}"); + } + if (!parser.TryConsume(out _)) - throw new InvalidDataException($"Cannot deserialize object of type {type.FullName}."); + Log.Error($"Cannot deserialize object of type {type.FullName}."); List coordinates = ListPool.Pool.Get(4); int i = 0; @@ -45,16 +65,17 @@ public object ReadYaml(IParser parser, Type type) continue; } - if (!parser.TryConsume(out Scalar scalar) || !float.TryParse(scalar.Value, NumberStyles.Float, CultureInfo.GetCultureInfo("en-US"), out float coordinate)) + if (!parser.TryConsume(out Scalar coordScalar) || + !float.TryParse(coordScalar.Value, NumberStyles.Float, CultureInfo.GetCultureInfo("en-US"), out float coordinate)) { ListPool.Pool.Return(coordinates); - throw new InvalidDataException($"Invalid float value."); + throw new InvalidDataException("Invalid float value."); } coordinates.Add(coordinate); } - object vector = Activator.CreateInstance(type, coordinates.ToArray()); + object vector = Activator.CreateInstance(baseType, coordinates.ToArray()); ListPool.Pool.Return(coordinates); @@ -64,6 +85,12 @@ public object ReadYaml(IParser parser, Type type) /// public void WriteYaml(IEmitter emitter, object value, Type type) { + if (value is null) + { + emitter.Emit(new Scalar("null")); + return; + } + Dictionary coordinates = DictionaryPool.Pool.Get(); if (value is Vector2 vector2) From 6399685ec11a5ae722525fa5e18eecdb63c60539 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:20:49 +0200 Subject: [PATCH 161/224] fix: Player.Count fixed & added setter on Round (#653) --- EXILED/Exiled.API/Features/Player.cs | 2 +- EXILED/Exiled.API/Features/Round.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 29f6d05028..ad08c77dfa 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -150,7 +150,7 @@ public Player(GameObject gameObject) /// /// /// - public static int Count => Dictionary.Count; + public static int Count => global::ReferenceHub.GetPlayerCount(CentralAuth.ClientInstanceMode.ReadyClient, CentralAuth.ClientInstanceMode.Host); /// /// Gets a containing cached and their user ids. diff --git a/EXILED/Exiled.API/Features/Round.cs b/EXILED/Exiled.API/Features/Round.cs index a278e169dc..38bb4407ed 100644 --- a/EXILED/Exiled.API/Features/Round.cs +++ b/EXILED/Exiled.API/Features/Round.cs @@ -122,9 +122,13 @@ public static int Kills } /// - /// Gets the number of surviving SCPs. + /// Gets or sets the number of surviving SCPs. /// - public static int SurvivingSCPs => RoundSummary.SurvivingSCPs; + public static int SurvivingSCPs + { + get => RoundSummary.SurvivingSCPs; + set => RoundSummary.SurvivingSCPs = value; + } /// /// Gets or sets the number of kills made by SCPs. @@ -163,9 +167,13 @@ public static ServerStatic.NextRoundAction NextRoundAction } /// - /// Gets the number of rounds since the server started. + /// Gets or sets the number of rounds since the server started. /// - public static int UptimeRounds => RoundRestart.UptimeRounds; + public static int UptimeRounds + { + get => RoundRestart.UptimeRounds; + set => RoundRestart.UptimeRounds = value; + } /// /// Gets a indicating the sides that are currently alive. From 01f224111a71d3e8942eb59bef2999af0cf53d55 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:14:03 +0200 Subject: [PATCH 162/224] Warn not Error & Docs --- EXILED/Exiled.API/Features/Camera.cs | 4 ++-- .../Features/Components/CollisionHandler.cs | 16 +++++----------- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- .../Exiled.API/Features/Pickups/KeycardPickup.cs | 2 +- EXILED/Exiled.API/Features/Player.cs | 4 +--- EXILED/Exiled.API/Features/Window.cs | 5 ++--- .../Parsers/AggregateExpectationTypeResolver.cs | 2 +- 7 files changed, 13 insertions(+), 22 deletions(-) diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index 1b03ab2a4d..e8f015a13b 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -167,7 +167,7 @@ internal Camera(Scp079Camera camera079) } else { - Log.Error($"[Camera] Add CameraType entry for: [Room: {Room?.Type ?? RoomType.Unknown} Name: {Name}]"); + Log.Warn($"[Camera] Add CameraType entry for: [Room: {Room?.Type ?? RoomType.Unknown} Name: {Name}]"); } } } @@ -209,7 +209,7 @@ internal Camera(Scp079Camera camera079) public ushort Id => Base.SyncId; /// - /// Gets the generator's . + /// Gets the camera's . /// public Room Room => room ??= Room.Get(Base.Room); diff --git a/EXILED/Exiled.API/Features/Components/CollisionHandler.cs b/EXILED/Exiled.API/Features/Components/CollisionHandler.cs index 54d117fe48..cf09ae1457 100644 --- a/EXILED/Exiled.API/Features/Components/CollisionHandler.cs +++ b/EXILED/Exiled.API/Features/Components/CollisionHandler.cs @@ -50,17 +50,11 @@ private void OnCollisionEnter(Collision collision) { if (!initialized) return; - if (Owner == null) - Log.Error($"Owner is null!"); - if (Grenade == null) - Log.Error("Grenade is null!"); - if (collision is null) - Log.Error("wat"); - if (!collision.collider) - Log.Error("water"); - if (collision.collider.gameObject == null) - Log.Error("pepehm"); - if (collision.collider.gameObject == Owner || collision.collider.gameObject.TryGetComponent(out _)) + if (Owner == null || Grenade == null) + return; + if (collision?.collider?.gameObject == Owner) + return; + if (collision.collider.gameObject.TryGetComponent(out _)) return; Grenade.TargetTime = 0.1f; diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 52bc80b55a..b2410ac6b1 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -55,7 +55,7 @@ internal Door(DoorVariant door, List rooms) Type = GetDoorType(); if (Base != null && Type is DoorType.UnknownDoor or DoorType.UnknownGate or DoorType.UnknownElevator) - Log.Error($"[DoorType] Room: {Room?.Type ?? RoomType.Unknown} Name:{Name} GameObjectName:{GameObject.name}"); + Log.Warn($"[DoorType] Type: {Type} Room: {Room?.Type ?? RoomType.Unknown} Name:{Name} GameObjectName:{GameObject.name}"); } /// diff --git a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs index 12a4b8701e..5a80eb35ca 100644 --- a/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/KeycardPickup.cs @@ -32,7 +32,7 @@ internal KeycardPickup(BaseKeycard pickupBase) Base = pickupBase; if (Base is null) { - Log.Error($"[KeycardPickup] Base is null: {GetType()}"); + Log.Warn($"[KeycardPickup] Base is null: {GetType()}"); } } diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index ad08c77dfa..80b9844f49 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -373,7 +373,7 @@ public string CustomInfo { if (!NicknameSync.ValidateCustomInfo(value, out string rejectionText)) { - Log.Error($"Could not set CustomInfo for {Nickname}. Reason: {rejectionText}"); + Log.Warn($"Could not set CustomInfo for {Nickname}. Reason: {rejectionText}"); } InfoArea = string.IsNullOrEmpty(value) ? InfoArea & ~PlayerInfoArea.CustomInfo : InfoArea |= PlayerInfoArea.CustomInfo; @@ -2580,7 +2580,6 @@ public void ResetAmmoLimit(AmmoType ammoType) { if (!HasCustomAmmoLimit(ammoType)) { - Log.Error($"{nameof(Player)}.{nameof(ResetAmmoLimit)}(AmmoType): AmmoType.{ammoType} does not have a custom limit."); return; } @@ -2673,7 +2672,6 @@ public void ResetCategoryLimit(ItemCategory category) if (!HasCustomCategoryLimit(category)) { - Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory): ItemCategory.{category} does not have a custom limit."); return; } diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index cc99dbcb61..bb45bccbd3 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -38,10 +38,9 @@ internal Window(BreakableWindow window, Room room) Base = window; Room = room; Type = GetGlassType(); -#if DEBUG + if (Type is GlassType.Unknown) - Log.Error($"[GLASSTYPE UNKNOWN] {this} BASE = {Base}"); -#endif + Log.Warn($"[GLASSTYPE UNKNOWN] Room = ({Room}) BASE = [{Base}]"); } /// diff --git a/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs b/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs index abf9f24f25..646c34bb50 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs @@ -45,7 +45,7 @@ public AggregateExpectationTypeResolver(INamingConvention namingConvention) } catch (Exception e) { - Log.Error($"Error loading types for {assembly.FullName}. It can be ignored if it's not using Exiled.CustomRoles."); + Log.Warn($"Error loading types for {assembly.FullName}. It can be ignored if it's not using Exiled.CustomRoles. (Enabled Debug for more detail.)"); Log.Debug(e); } } From f6b512f7582a956c0216c231ff2b0588b9d6d16b Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:56:02 +0200 Subject: [PATCH 163/224] 14.2.0 build fix (#664) * 14.2.0 build fix * Update TT file * Fix DamagingDoor and add IPlayer * Fix Scp330 crash when dropping * Not Finish Enum (i just save jut in case) * Update Window::Type Finder * Add New Camera * fix: all patches fix * feat: new event * Fix LabAPI Versioning (I know that will not return the real value we already have our own system) * Fix InteractingEmergencyButton * New EffectType NightVison and Halloween * ref: installer qol change just add `--skip-version-select` param and now you can... skip this version select! * feat: wrapper for emerg.rel.but. * swap scp079 GlassType * Add LastHumanTracker.TryGetLastTarget compatability w Round.IgnoredPlayers * 9.10.0-rc.1 --------- Co-authored-by: VALERA771 Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> --- EXILED/EXILED.props | 2 +- EXILED/Exiled.API/Enums/CameraType.cs | 8 ++ EXILED/Exiled.API/Enums/DoorType.cs | 7 +- EXILED/Exiled.API/Enums/EffectType.cs | 77 +++++++++++++ EXILED/Exiled.API/Enums/GlassType.cs | 15 +++ EXILED/Exiled.API/Enums/PrefabType.cs | 6 + EXILED/Exiled.API/Enums/RoomType.cs | 15 +++ .../Extensions/EffectTypeExtension.cs | 13 +++ EXILED/Exiled.API/Features/Camera.cs | 5 + EXILED/Exiled.API/Features/Doors/Door.cs | 3 +- .../Exiled.API/Features/Doors/ElevatorDoor.cs | 2 +- .../Features/Doors/EmergencyReleaseButton.cs | 106 ++++++++++++++++++ EXILED/Exiled.API/Features/Lift.cs | 2 +- EXILED/Exiled.API/Features/Plugin.cs | 4 +- .../Exiled.API/Features/Roles/Scp049Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp079Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp096Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp106Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp1507Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp173Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp3114Role.cs | 10 ++ .../Exiled.API/Features/Roles/Scp939Role.cs | 10 ++ EXILED/Exiled.API/Features/Room.cs | 12 +- EXILED/Exiled.API/Features/Server.cs | 2 +- EXILED/Exiled.API/Features/Waves/TimedWave.cs | 9 +- EXILED/Exiled.API/Features/Window.cs | 48 ++++++-- .../Map/AnnouncingNtfEntranceEventArgs.cs | 2 + .../EventArgs/Player/DamagingDoorEventArgs.cs | 19 +++- .../InteractingEmergencyButtonEventArgs.cs | 49 ++++++++ .../Scp330/DroppingScp330EventArgs.cs | 7 +- EXILED/Exiled.Events/Handlers/Player.cs | 11 ++ .../Events/Map/AnnouncingNtfEntrance.cs | 10 +- .../Events/Map/AnnouncingNtfMiniEntrance.cs | 80 +++++++++++++ .../Events/Map/AnnouncingTeamEntrance.cs | 3 +- .../Patches/Events/Player/DamagingDoor.cs | 17 ++- .../Player/InteractingEmergencyButton.cs | 71 ++++++++++++ .../Events/Player/SavingByAntiScp207.cs | 3 +- .../Patches/Events/Scp049/Attacking.cs | 3 +- .../Patches/Events/Scp330/DroppingCandy.cs | 31 ++--- .../Patches/Events/Scp939/PlayingFootstep.cs | 4 +- .../Patches/Generic/LastTarget.cs | 52 +++++++++ EXILED/Exiled.Installer/CommandSettings.cs | 10 ++ EXILED/Exiled.Installer/Program.cs | 2 +- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- EXILED/Exiled.Loader/LoaderPlugin.cs | 3 +- .../SCPSLRessources/NW_Documentation.md | 106 +++++++++++++++++- 46 files changed, 822 insertions(+), 79 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Doors/EmergencyReleaseButton.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Player/InteractingEmergencyButtonEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfMiniEntrance.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Player/InteractingEmergencyButton.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/LastTarget.cs diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 589a0072b1..bfecd8b8fe 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.9.2 + 9.10.0-rc.1 false diff --git a/EXILED/Exiled.API/Enums/CameraType.cs b/EXILED/Exiled.API/Enums/CameraType.cs index 0a64a50d02..4b5a3a771f 100644 --- a/EXILED/Exiled.API/Enums/CameraType.cs +++ b/EXILED/Exiled.API/Enums/CameraType.cs @@ -151,5 +151,13 @@ public enum CameraType LczCameraToy, SzCameraToy, #endregion + + #region new + HczDss12, + EzGateAInterior, + EzGateAElevators, + EzGateBInterior, + EzGateBSide, + #endregion } } diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index 2d2b0a24f8..87e0de99a5 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -276,7 +276,7 @@ public enum DoorType UnknownElevator, /// - /// Represents the Elevator door for . + /// Represents the Elevator door for or . /// ElevatorGateA, @@ -364,5 +364,10 @@ public enum DoorType /// Represents the door used for Checkpoint. /// Checkpoint, + + /// + /// Represents the door in the Armory. + /// + GateAArmory, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index 12d94e1d49..2121e5a591 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -308,5 +308,82 @@ public enum EffectType /// . /// Fade, + + /// + /// . + /// + NightVision, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + Metal, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + OrangeCandy, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + OrangeWitness, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + Prismatic, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + SlowMetabolism, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + Spicy, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + SugarCrave, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + SugarHigh, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + SugarRush, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + TemporaryBypass, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + TraumatizedByEvil, + + /// + /// . + /// + [Obsolete("Only availaible for Halloween.")] + WhiteCandy, } } diff --git a/EXILED/Exiled.API/Enums/GlassType.cs b/EXILED/Exiled.API/Enums/GlassType.cs index 4144c7fd98..a1822d676e 100644 --- a/EXILED/Exiled.API/Enums/GlassType.cs +++ b/EXILED/Exiled.API/Enums/GlassType.cs @@ -74,5 +74,20 @@ public enum GlassType /// Represents the window in . /// Scp127, + + /// + /// Represents the window in . + /// + GateAPit, + + /// + /// Represents the window in . + /// + GateAArmory, + + /// + /// Represents the window in . + /// + Incinerator, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index 243470f07d..a67180d52e 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -381,5 +381,11 @@ public enum PrefabType [Prefab(3938583646, "WaypointToy")] WaypointToy, + + [Prefab(1891631329, "PrismaticCloud")] + PrismaticCloud, + + [Prefab(3938583646, "TantrumObj (Brown Candy)")] + TantrumObjBrownCandy, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/RoomType.cs b/EXILED/Exiled.API/Enums/RoomType.cs index 6e236c25d1..7333ab04dd 100644 --- a/EXILED/Exiled.API/Enums/RoomType.cs +++ b/EXILED/Exiled.API/Enums/RoomType.cs @@ -294,6 +294,11 @@ public enum RoomType /// HczCrossRoomWater, + /// + /// Heavy Containment Zone's cross room with waterfall. + /// + Dss08 = HczCrossRoomWater, + /// /// Heavy Containment Zone's corner. /// @@ -338,5 +343,15 @@ public enum RoomType /// Heavy Containment Zone's storage / server room. /// HczServerRoom, + + /// + /// Heavy Containment Zone's straight hall room with lava. + /// + HczIncineratorWayside, + + /// + /// Heavy Containment Zone's straight hall room with lava. + /// + HczDss12 = HczIncineratorWayside, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index c6b2df1164..aca90b95f3 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -80,12 +80,25 @@ public static class EffectTypeExtension { EffectType.Lightweight, typeof(Lightweight) }, { EffectType.HeavyFooted, typeof(HeavyFooted) }, { EffectType.Fade, typeof(Fade) }, + { EffectType.NightVision, typeof(NightVision) }, #pragma warning disable CS0618 { EffectType.Marshmallow, typeof(MarshmallowEffect) }, { EffectType.BecomingFlamingo, typeof(BecomingFlamingo) }, { EffectType.Scp559, typeof(Scp559Effect) }, { EffectType.Scp956Target, typeof(Scp956Target) }, { EffectType.Snowed, typeof(Snowed) }, + { EffectType.Metal, typeof(Metal) }, + { EffectType.OrangeCandy, typeof(OrangeCandy) }, + { EffectType.OrangeWitness, typeof(OrangeWitness) }, + { EffectType.Prismatic, typeof(Prismatic) }, + { EffectType.SlowMetabolism, typeof(SlowMetabolism) }, + { EffectType.Spicy, typeof(Spicy) }, + { EffectType.SugarCrave, typeof(SugarCrave) }, + { EffectType.SugarHigh, typeof(SugarHigh) }, + { EffectType.SugarRush, typeof(SugarRush) }, + { EffectType.TemporaryBypass, typeof(TemporaryBypass) }, + { EffectType.TraumatizedByEvil, typeof(TraumatizedByEvil) }, + { EffectType.WhiteCandy, typeof(WhiteCandy) }, #pragma warning restore CS0618 }); diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index e8f015a13b..dc3247a7b3 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -139,6 +139,11 @@ public class Camera : IWrapper, IWorldSpace ["HCZ SERVERS UPPER STORAGE"] = CameraType.HczServersUpperStorage, ["HCZ LOWER SERVER STORAGE"] = CameraType.HczLowerServerStorage, ["HCZ SERVERS STAIRCASE"] = CameraType.HczServerStaircase, + ["DSS-12"] = CameraType.HczDss12, + ["GATE A INTERIOR"] = CameraType.EzGateAInterior, + ["GATE A ELEVATORS"] = CameraType.EzGateAElevators, + ["GATE B INTERIOR"] = CameraType.EzGateBInterior, + ["GATE B SIDE"] = CameraType.EzGateBSide, // CustomCamera ["EZ ARM CAMERA TOY"] = CameraType.EzArmCameraToy, diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index b2410ac6b1..19718900a7 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -623,6 +623,7 @@ private DoorType GetDoorType() { RoomType.HczEzCheckpointA => DoorType.CheckpointArmoryA, RoomType.HczEzCheckpointB => DoorType.CheckpointArmoryB, + RoomType.EzGateA => DoorType.GateAArmory, _ => DoorType.UnknownDoor, }, "Unsecured Pryable GateDoor" => Room?.Type switch @@ -638,7 +639,7 @@ private DoorType GetDoorType() { ElevatorGroup.Scp049 => DoorType.ElevatorScp049, ElevatorGroup.GateB => DoorType.ElevatorGateB, - ElevatorGroup.GateA => DoorType.ElevatorGateA, + ElevatorGroup.GateA01 or ElevatorGroup.GateA02 => DoorType.ElevatorGateA, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => DoorType.ElevatorLczA, ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => DoorType.ElevatorLczB, _ => DoorType.UnknownElevator, diff --git a/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs b/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs index 0145e53f84..8d455e6653 100644 --- a/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs +++ b/EXILED/Exiled.API/Features/Doors/ElevatorDoor.cs @@ -54,7 +54,7 @@ internal ElevatorDoor(Interactables.Interobjects.ElevatorDoor door, List r public ElevatorType ElevatorType => Group switch { ElevatorGroup.Scp049 => ElevatorType.Scp049, - ElevatorGroup.GateA => ElevatorType.GateA, + ElevatorGroup.GateA01 or ElevatorGroup.GateA02 => ElevatorType.GateA, ElevatorGroup.GateB => ElevatorType.GateB, ElevatorGroup.ServerRoom => ElevatorType.ServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => ElevatorType.LczA, diff --git a/EXILED/Exiled.API/Features/Doors/EmergencyReleaseButton.cs b/EXILED/Exiled.API/Features/Doors/EmergencyReleaseButton.cs new file mode 100644 index 0000000000..4fb094f639 --- /dev/null +++ b/EXILED/Exiled.API/Features/Doors/EmergencyReleaseButton.cs @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Doors +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Exiled.API.Interfaces; + using Interactables.Interobjects; + + /// + /// A wrapper for . + /// + public class EmergencyReleaseButton : IWrapper + { + /// + /// Dictionary containing base object with linked wrapper. + /// + internal static readonly Dictionary ObjectToWrapper = new(); + + /// + /// Initializes a new instance of the class. + /// + /// + internal EmergencyReleaseButton(EmergencyDoorRelease @base) + { + Base = @base; + Door = Door.Get(Base._controlledDoor); + + ObjectToWrapper[@base] = this; + } + + /// + /// Gets all instaces of . + /// + public static IReadOnlyCollection List => ObjectToWrapper.Values; + + /// + public EmergencyDoorRelease Base { get; } + + /// + /// Gets the door which will be opened if this button is interacted. + /// + public Door Door { get; } + + /// + /// Gets or sets a value indicating whether button can be used. + /// + public bool IsReady + { + get => Base._isReady; + set => Base._isReady = value; + } + + /// + /// Gets or sets default time after which button can be used again. + /// + public float InitialTimer + { + get => Base._initialTimer; + set => Base._initialTimer = value; + } + + /// + /// Gets or sets time after which button can be used again. + /// + public float ReleaseTimer + { + get => Base._releaseTimer; + set => Base._releaseTimer = value; + } + + /// + /// Gets an by it's base-game instance. + /// + /// The instance. + /// An instance if found. Otherwise, null. + public static EmergencyReleaseButton Get(EmergencyDoorRelease emergencyDoorRelease) + { + if (ObjectToWrapper.TryGetValue(emergencyDoorRelease, out EmergencyReleaseButton wrapper)) + return wrapper; + + return new(emergencyDoorRelease); + } + + /// + /// Gets an by door. + /// + /// Door which is linked with . + /// An instance if found. Otherwise, null. + public static EmergencyReleaseButton Get(Door door) => Get(x => x.Door == door).FirstOrDefault(); + + /// + /// Gets all according to the condition. + /// + /// Condition to satisfy. + /// An of found instances. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index 01f0219a97..de7dcfabb7 100644 --- a/EXILED/Exiled.API/Features/Lift.cs +++ b/EXILED/Exiled.API/Features/Lift.cs @@ -135,7 +135,7 @@ public ElevatorSequence Status public ElevatorType Type => Group switch { ElevatorGroup.Scp049 => ElevatorType.Scp049, - ElevatorGroup.GateA => ElevatorType.GateA, + ElevatorGroup.GateA01 or ElevatorGroup.GateA02 => ElevatorType.GateA, ElevatorGroup.GateB => ElevatorType.GateB, ElevatorGroup.ServerRoom => ElevatorType.ServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => ElevatorType.LczA, diff --git a/EXILED/Exiled.API/Features/Plugin.cs b/EXILED/Exiled.API/Features/Plugin.cs index 8884b26068..cebeab971f 100644 --- a/EXILED/Exiled.API/Features/Plugin.cs +++ b/EXILED/Exiled.API/Features/Plugin.cs @@ -143,7 +143,7 @@ public virtual void OnRegisteringCommands() if (commandHandlerType == typeof(RemoteAdminCommandHandler)) CommandProcessor.RemoteAdminCommandHandler.RegisterCommand(command); else if (commandHandlerType == typeof(GameConsoleCommandHandler)) - GameCore.Console.singleton.ConsoleCommandHandler.RegisterCommand(command); + GameCore.Console.ConsoleCommandHandler.RegisterCommand(command); else if (commandHandlerType == typeof(ClientCommandHandler)) QueryProcessor.DotCommandHandler.RegisterCommand(command); } @@ -212,7 +212,7 @@ public virtual void OnUnregisteringCommands() if (types.Key == typeof(RemoteAdminCommandHandler)) CommandProcessor.RemoteAdminCommandHandler.UnregisterCommand(command); else if (types.Key == typeof(GameConsoleCommandHandler)) - GameCore.Console.singleton.ConsoleCommandHandler.UnregisterCommand(command); + GameCore.Console.ConsoleCommandHandler.UnregisterCommand(command); else if (types.Key == typeof(ClientCommandHandler)) QueryProcessor.DotCommandHandler.UnregisterCommand(command); } diff --git a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs index 6a58fcd444..1c015e2ff0 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs @@ -123,6 +123,16 @@ internal Scp049Role(Scp049GameRole baseRole) /// public Dictionary ResurrectedPlayers => Scp049ResurrectAbility.ResurrectedPlayers.ToDictionary(x => Player.Get(x.Key), x => x.Value); + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets or sets the amount of time before SCP-049 can use its Doctor's Call ability again. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs index c2b8cb70ce..9d7e308659 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs @@ -236,6 +236,16 @@ public Camera Camera /// public Door LockedDoor => Door.Get(DoorLockChanger.LockedDoor); + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets or sets SCP-079's abilities. Can be . /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs index ff17c6bcb2..a59e699ac9 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs @@ -109,6 +109,16 @@ internal Scp096Role(Scp096GameRole baseRole) /// public Scp096ChargeAbility ChargeAbility { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets a value indicating SCP-096's ability state. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp106Role.cs b/EXILED/Exiled.API/Features/Roles/Scp106Role.cs index 067ee69647..8c5fda149a 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp106Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp106Role.cs @@ -110,6 +110,16 @@ internal Scp106Role(Scp106GameRole baseRole) /// public Scp106MovementModule MovementModule { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets or sets SCP-106's Vigor Level. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs b/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs index 559fdd6623..a4c9ba78cf 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs @@ -49,6 +49,16 @@ internal Scp1507Role(Scp1507GameRole baseRole) /// public new Scp1507GameRole Base { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets the Spawn Chance of SCP-939. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp173Role.cs b/EXILED/Exiled.API/Features/Roles/Scp173Role.cs index 3737c17dbe..f7f603eece 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp173Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp173Role.cs @@ -118,6 +118,16 @@ internal Scp173Role(Scp173GameRole baseRole) /// public Scp173AudioPlayer AudioPlayer { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets or sets the amount of time before SCP-173 can use breakneck speed again. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs index f5ba440058..35a3aa032a 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs @@ -131,6 +131,16 @@ internal Scp3114Role(Scp3114GameRole baseRole) /// public new Scp3114GameRole Base { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets the damage amount of SCP-3114's slap ability. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp939Role.cs b/EXILED/Exiled.API/Features/Roles/Scp939Role.cs index 4396f8f87b..222a57670d 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp939Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp939Role.cs @@ -144,6 +144,16 @@ internal Scp939Role(Scp939GameRole baseRole) /// public MimicryRecorder MimicryRecorder { get; } + /// + /// Gets a value indicating whether . + /// + public bool RespectPreferences => true; + + /// + /// Gets a value indicating whether . + /// + public bool AllowFallback => true; + /// /// Gets or sets the amount of time before SCP-939 can attack again. /// diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index b472bf10f5..3e971fecb2 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -429,15 +429,14 @@ internal void InternalCreate() RoomIdentifierToRoom.Add(Identifier, this); Zone = Identifier.Zone.GetZone(); -#if DEBUG + if (Zone is ZoneType.Unspecified) - Log.Error($"[ZONETYPE UNKNOWN] {this} Zone : {Identifier?.Zone}"); -#endif + Log.Warn($"[ZONETYPE UNKNOWN] {Identifier} Zone : {Identifier?.Zone}"); + Type = FindType(gameObject); -#if DEBUG + if (Type is RoomType.Unknown) - Log.Error($"[ROOMTYPE UNKNOWN] {this} Name : {gameObject?.name} Shape : {Identifier?.Shape}"); -#endif + Log.Warn($"[ROOMTYPE UNKNOWN] {Identifier} Name : {gameObject?.name} Shape : {Identifier?.Shape}"); RoomLightControllers = RoomLightControllersValue.AsReadOnly(); @@ -485,6 +484,7 @@ private static RoomType FindType(GameObject gameObject) "HCZ_TArmory" => RoomType.HczArmory, "HCZ_MicroHID_New" => RoomType.HczHid, "HCZ_Crossroom_Water" => RoomType.HczCrossRoomWater, + "HCZ_IncineratorWayside" => RoomType.HczIncineratorWayside, "HCZ_Testroom" => RoomType.HczTestRoom, "HCZ_049" => RoomType.Hcz049, "HCZ_079" => RoomType.Hcz079, diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index e07764d9e9..64b1287959 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -317,7 +317,7 @@ public static void ShutdownRedirect(ushort redirectPort, bool quit, bool suppres /// The command to be run. /// The running the command. /// Command response, if there is one; otherwise, . - public static string ExecuteCommand(string command, CommandSender sender = null) => GameCore.Console.singleton.TypeCommand(command, sender); + public static string ExecuteCommand(string command, CommandSender sender = null) => GameCore.Console.Singleton.TypeCommand(command, sender); /// /// Safely gets an from , then casts it to . diff --git a/EXILED/Exiled.API/Features/Waves/TimedWave.cs b/EXILED/Exiled.API/Features/Waves/TimedWave.cs index 346bcd6c11..8856727c58 100644 --- a/EXILED/Exiled.API/Features/Waves/TimedWave.cs +++ b/EXILED/Exiled.API/Features/Waves/TimedWave.cs @@ -193,6 +193,13 @@ public static List GetTimedWaves() /// Plays the announcement for this wave. /// /// Wave must implement . - public void PlayAnnouncement() => Announcement?.PlayAnnouncement(); + public void PlayAnnouncement() => Announcement?.PlayAnnouncement(new()); + + /// + /// Plays the announcement for this wave. + /// + /// Wave must implement . + /// The list of Player to spawn. + public void PlayAnnouncement(IEnumerable players) => Announcement?.PlayAnnouncement(players.Select(x => x.ReferenceHub).ToList()); } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index bb45bccbd3..01e1c698e4 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -13,6 +13,7 @@ namespace Exiled.API.Features using DamageHandlers; using Enums; + using Exiled.API.Extensions; using Exiled.API.Features.Doors; using Exiled.API.Interfaces; using UnityEngine; @@ -40,7 +41,7 @@ internal Window(BreakableWindow window, Room room) Type = GetGlassType(); if (Type is GlassType.Unknown) - Log.Warn($"[GLASSTYPE UNKNOWN] Room = ({Room}) BASE = [{Base}]"); + Log.Warn($"[GLASSTYPE UNKNOWN] Room = ({Room}) BASE = [{Base}] HP = {{{Base?.Health}}}"); } /// @@ -214,18 +215,41 @@ public void DamageWindow(float amount, DamageHandlerBase handler) /// A string containing Window-related data. public override string ToString() => $"{Type} ({Health}) [{IsBroken}] *{DisableScpDamage}*"; - private GlassType GetGlassType() => Room?.Type switch + private GlassType GetGlassType() => Base.name.RemoveBracketsOnEndOfName() switch { - RoomType.Lcz330 => GlassType.Scp330, - RoomType.LczGlassBox => GlassType.GR18, - RoomType.LczPlants => GlassType.Plants, - RoomType.Hcz049 => GlassType.Scp049, - RoomType.Hcz079 => Base._preventScpDamage ? GlassType.Scp079Trigger : GlassType.Scp079, - RoomType.HczHid => GlassType.MicroHid, - RoomType.HczTestRoom => GlassType.TestRoom, - RoomType.HczEzCheckpointA => GlassType.HczEzCheckpointA, - RoomType.HczEzCheckpointB => GlassType.HczEzCheckpointB, - RoomType.Hcz127 => GlassType.Scp127, + "B272sa" => Room?.Type switch + { + RoomType.LczGlassBox => GlassType.GR18, + RoomType.Lcz330 => GlassType.Scp330, + _ => GlassType.Unknown, + }, + "GLASS" => Room?.Type switch + { + RoomType.Hcz079 => GlassType.Scp079, + RoomType.HczHid => GlassType.MicroHid, + RoomType.HczEzCheckpointA => GlassType.HczEzCheckpointA, + RoomType.HczEzCheckpointB => GlassType.HczEzCheckpointB, + RoomType.EzGateA when Base.name[7] == '5' => GlassType.GateAArmory, + RoomType.EzGateA => GlassType.GateAPit, + _ => GlassType.Unknown, + }, + "Window" => Room?.Type switch + { + RoomType.Hcz049 => GlassType.Scp049, + RoomType.Hcz127 => GlassType.Scp127, + RoomType.HczHid => GlassType.MicroHid, + RoomType.HczTestRoom => GlassType.TestRoom, + _ => GlassType.Unknown, + }, + "Glass" => Room?.Type switch + { + RoomType.Hcz079 => GlassType.Scp079Trigger, + RoomType.HczHid => GlassType.MicroHid, + _ => GlassType.Unknown, + }, + "glass" => GlassType.Scp079, + "HCZ_IncineratorWayside" => GlassType.Incinerator, + "VTGLASS" => GlassType.Plants, _ => GlassType.Unknown, }; } diff --git a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingNtfEntranceEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingNtfEntranceEventArgs.cs index 40a9fd3151..fd5a8f417e 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingNtfEntranceEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingNtfEntranceEventArgs.cs @@ -44,11 +44,13 @@ public AnnouncingNtfEntranceEventArgs(WaveAnnouncementBase announcement, int scp /// /// Gets or sets the NTF unit name. /// + /// If is NTF Mini wave, value is null and setter does not work. public string UnitName { get; set; } /// /// Gets or sets the NTF unit number. /// + /// If is NTF Mini wave, value is 0 and setter does not work. public int UnitNumber { get; set; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs index c3e78bdfd6..4ed1981db1 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DamagingDoorEventArgs.cs @@ -7,14 +7,16 @@ namespace Exiled.Events.EventArgs.Player { + using Exiled.API.Features; using Exiled.API.Features.Doors; + using Footprinting; using Interactables.Interobjects.DoorUtils; using Interfaces; /// /// Contains all information before damage is dealt to a . /// - public class DamagingDoorEventArgs : IDeniableEvent + public class DamagingDoorEventArgs : IDeniableEvent, IPlayerEvent { /// /// Initializes a new instance of the class. @@ -26,11 +28,16 @@ public class DamagingDoorEventArgs : IDeniableEvent /// /// /// - public DamagingDoorEventArgs(DoorVariant door, float damage, DoorDamageType doorDamageType) + /// + /// + /// + public DamagingDoorEventArgs(DoorVariant door, float damage, DoorDamageType doorDamageType, Footprint footprint) { Door = Door.Get(door); Damage = damage; DamageType = doorDamageType; + Footprint = footprint; + Player = Player.Get(footprint); // TODO: Remove when NW fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/817 IsAllowed = damage > 0; @@ -55,5 +62,13 @@ public DamagingDoorEventArgs(DoorVariant door, float damage, DoorDamageType door /// Gets the dealt to the door. /// public DoorDamageType DamageType { get; } + + /// + public Player Player { get; } + + /// + /// Gets the of the player that damaged the door. + /// + public Footprint Footprint { get; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingEmergencyButtonEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingEmergencyButtonEventArgs.cs new file mode 100644 index 0000000000..9efbad6105 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingEmergencyButtonEventArgs.cs @@ -0,0 +1,49 @@ +// ----------------------------------------------------------------------- +// +// 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.API.Features.Doors; + using Exiled.Events.EventArgs.Interfaces; + using Interactables.Interobjects; + + /// + /// Contains all information before is pressed. + /// + public class InteractingEmergencyButtonEventArgs : IDoorEvent, IPlayerEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public InteractingEmergencyButtonEventArgs(EmergencyDoorRelease emergencyDoorRelease, Door door, Player player, bool isAllowed = true) + { + EmergencyReleaseButton = EmergencyReleaseButton.Get(emergencyDoorRelease); + Door = door; + Player = player; + IsAllowed = isAllowed; + } + + /// + /// Gets the that's being pressed. + /// + public EmergencyReleaseButton EmergencyReleaseButton { get; } + + /// + public Door Door { get; } + + /// + public Player Player { get; } + + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs index 0ead45776d..dbb905ee8a 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/DroppingScp330EventArgs.cs @@ -22,19 +22,16 @@ public class DroppingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableEven /// /// Initializes a new instance of the class. /// - /// - /// - /// /// /// /// /// /// /// - public DroppingScp330EventArgs(Player player, Scp330Bag scp330, CandyKindID candy) + public DroppingScp330EventArgs(Scp330Bag scp330, CandyKindID candy) { - Player = player; Scp330 = Item.Get(scp330); + Player = Scp330.Owner; Candy = candy; } diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index 90c721dfd8..97e6137003 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -616,6 +616,11 @@ public class Player [Obsolete("Never available (for now).")] public static Event DrinkingCoffee { get; set; } = new(); + /// + /// Invoked before Emergency Release Button is pressed. + /// + public static Event InteractingEmergencyButton { get; set; } = new(); + /// /// Called before a player's emotion changed. /// @@ -1345,5 +1350,11 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item /// /// The instance. public static void OnHit(HitEventArgs ev) => Hit.InvokeSafely(ev); + + /// + /// Called before Emergency Release Button is pressed. + /// + /// The instance. + public static void OnInteractingEmergencyButton(InteractingEmergencyButtonEventArgs ev) => InteractingEmergencyButton.InvokeSafely(ev); } } diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs index 4d169bf050..bf59ef0103 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfEntrance.cs @@ -23,19 +23,13 @@ namespace Exiled.Events.Patches.Events.Map using static HarmonyLib.AccessTools; /// - /// Patch the and . + /// Patch the /// Adds the event. /// [EventPatch(typeof(Map), nameof(Map.AnnouncingNtfEntrance))] - [HarmonyPatch] + [HarmonyPatch(typeof(NtfWaveAnnouncement), nameof(NtfWaveAnnouncement.CreateAnnouncementString))] internal static class AnnouncingNtfEntrance { - private static IEnumerable TargetMethods() - { - yield return Method(typeof(NtfWaveAnnouncement), nameof(NtfWaveAnnouncement.CreateAnnouncementString)); - yield return Method(typeof(NtfMiniwaveAnnouncement), nameof(NtfMiniwaveAnnouncement.CreateAnnouncementString)); - } - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfMiniEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfMiniEntrance.cs new file mode 100644 index 0000000000..8bb3389fdd --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingNtfMiniEntrance.cs @@ -0,0 +1,80 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Map +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Map; + using Handlers; + using HarmonyLib; + using Respawning.Announcements; + + using static HarmonyLib.AccessTools; + + /// + /// Patch the + /// Adds the event. + /// + [EventPatch(typeof(Map), nameof(Map.AnnouncingNtfEntrance))] + [HarmonyPatch(typeof(NtfMiniwaveAnnouncement), nameof(NtfMiniwaveAnnouncement.CreateAnnouncementString))] + internal static class AnnouncingNtfMiniEntrance + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(AnnouncingNtfEntranceEventArgs)); + + Label ret = generator.DefineLabel(); + + newInstructions.InsertRange( + 0, + new CodeInstruction[] + { + // WaveAnnouncementBase + new(OpCodes.Ldarg_0), + + // scpsLeft + new(OpCodes.Ldloc_0), + + // null + new(OpCodes.Ldnull), + + // 0 + new(OpCodes.Ldc_I4_0), + + // AnnouncingNtfEntranceEventArgs ev = new(this, scpsLeft, null, 0); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(AnnouncingNtfEntranceEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + new(OpCodes.Call, Method(typeof(Map), nameof(Map.OnAnnouncingNtfEntrance))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingNtfEntranceEventArgs), nameof(AnnouncingNtfEntranceEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, ret), + + // scpsLeft = ev.ScpsLeft; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingNtfEntranceEventArgs), nameof(AnnouncingNtfEntranceEventArgs.ScpsLeft))), + new(OpCodes.Stloc_0), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(ret); + + 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/Map/AnnouncingTeamEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs index b1a968cade..7193f8d17f 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs @@ -38,7 +38,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.Calls(Method(typeof(WaveAnnouncementBase), nameof(WaveAnnouncementBase.SendSubtitles)))); CodeInstruction sendSubtitlesInstruction = newInstructions[index]; - newInstructions.RemoveRange(index - 1, 2); + newInstructions.RemoveRange(index - 2, 3); index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldsfld); @@ -53,6 +53,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Ret) + offset; - int offset = -3; - int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldarg_1) + offset; + newInstructions[index].labels.Add(ret); + + offset = -3; + index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldarg_1) + offset; newInstructions.InsertRange( index, @@ -54,7 +58,10 @@ private static IEnumerable Transpiler(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.Player +{ + using System.Collections.Generic; + using System.Linq; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Doors; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using Interactables.Interobjects; + using Interactables.Interobjects.DoorUtils; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.InteractingEmergencyButton))] + [HarmonyPatch(typeof(EmergencyDoorRelease), nameof(EmergencyDoorRelease.ServerInteract), typeof(ReferenceHub), typeof(byte))] + internal class InteractingEmergencyButton + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label transpilerLabel = generator.DefineLabel(); + + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Brtrue_S) + 2; + Label continueLabel = (Label)newInstructions[index - 2].operand; + newInstructions[index - 2].operand = transpilerLabel; + + newInstructions.InsertRange(index, new[] + { + new CodeInstruction(OpCodes.Ldarg_0).WithLabels(transpilerLabel), + + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(EmergencyDoorRelease), nameof(EmergencyDoorRelease._controlledDoor))), + new(OpCodes.Call, typeof(Door).GetMethods().Single(method => method.Name is "Get" && !method.IsGenericMethod && method.GetParameters().All(parameter => parameter.ParameterType == typeof(DoorVariant)))), + + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + new(OpCodes.Ldc_I4_1), + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InteractingEmergencyButtonEventArgs))[0]), + new(OpCodes.Dup), + + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnInteractingEmergencyButton))), + + new(OpCodes.Callvirt, PropertyGetter(typeof(InteractingEmergencyButtonEventArgs), nameof(InteractingEmergencyButtonEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, continueLabel), + new(OpCodes.Ret), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs index e3275e0a95..ac4157b7ce 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs @@ -33,8 +33,7 @@ private static IEnumerable Transpiler(IEnumerable i.Calls(Method(typeof(StatusEffectBase), nameof(StatusEffectBase.DisableEffect)))) + offset; + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldloc_1); Label skipLabel = generator.DefineLabel(); Label gotoEventLabel = newInstructions[index].labels[0]; diff --git a/EXILED/Exiled.Events/Patches/Events/Scp049/Attacking.cs b/EXILED/Exiled.Events/Patches/Events/Scp049/Attacking.cs index 6e36a101b6..0786079299 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp049/Attacking.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp049/Attacking.cs @@ -32,8 +32,7 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - int offset = -3; - int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(AbilityCooldown), nameof(AbilityCooldown.Trigger)))) + offset; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldc_R4); Label continueLabel = generator.DefineLabel(); diff --git a/EXILED/Exiled.Events/Patches/Events/Scp330/DroppingCandy.cs b/EXILED/Exiled.Events/Patches/Events/Scp330/DroppingCandy.cs index 7cadf1189f..6004294efa 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp330/DroppingCandy.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp330/DroppingCandy.cs @@ -50,16 +50,11 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable i.opcode == OpCodes.Stloc_2); - newInstructions.InsertRange( - index, - new CodeInstruction[] - { - // candyKindID = ev.Candy, save locally. - new(OpCodes.Pop), + // index = GetCandyID(bag, candyKindID) + new(OpCodes.Ldarg_0), new(OpCodes.Ldloc, ev.LocalIndex), new(OpCodes.Callvirt, PropertyGetter(typeof(DroppingScp330EventArgs), nameof(DroppingScp330EventArgs.Candy))), + new(OpCodes.Call, Method(typeof(DroppingCandy), nameof(DroppingCandy.GetIDCandy))), + new(OpCodes.Starg_S, 1), + + // if (index == -1) + // return; + new(OpCodes.Ldarg_1), + new(OpCodes.Ldc_I4_M1), + new(OpCodes.Beq, returnLabel), }); newInstructions[newInstructions.Count - 1].labels.Add(returnLabel); @@ -104,5 +100,10 @@ private static CandyKindID GetCandyID(Scp330Bag scp330Bag, int index) return CandyKindID.None; return scp330Bag.Candies[index]; } + + private static int GetIDCandy(Scp330Bag scp330Bag, CandyKindID candyKindId) + { + return scp330Bag.Candies.FindIndex(x => candyKindId == x); + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Scp939/PlayingFootstep.cs b/EXILED/Exiled.Events/Patches/Events/Scp939/PlayingFootstep.cs index 94d40a79fc..e0d0485430 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp939/PlayingFootstep.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp939/PlayingFootstep.cs @@ -37,8 +37,8 @@ private static IEnumerable Transpiler(IEnumerable i.Calls(Method(typeof(RippleTriggerBase), nameof(RippleTriggerBase.CheckVisibility)))) + offset; + int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ret) + offset; newInstructions.InsertRange(index, new CodeInstruction[] { diff --git a/EXILED/Exiled.Events/Patches/Generic/LastTarget.cs b/EXILED/Exiled.Events/Patches/Generic/LastTarget.cs new file mode 100644 index 0000000000..442a5fbd63 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/LastTarget.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using System.Collections.Generic; + using System.Linq; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using HarmonyLib; + using PlayerRoles.PlayableScps.HumanTracker; + + using static HarmonyLib.AccessTools; + + /// + /// Patches so does not count to it. + /// + [HarmonyPatch(typeof(LastHumanTracker), nameof(LastHumanTracker.TryGetLastTarget))] + public class LastTarget + { + private static IEnumerable Transpiler(IEnumerable instructions) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label continueLabel = (Label)newInstructions.First(instruction => instruction.opcode == OpCodes.Br_S).operand; + + // can break between versions! + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stloc_3) + 1; + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // Round.IgnoredPlayers.Contains(allHub) + new(OpCodes.Call, PropertyGetter(typeof(Round), nameof(Round.IgnoredPlayers))), + new(OpCodes.Ldloc_3), + new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Contains))), + + new(OpCodes.Brtrue_S, continueLabel), + }); + + for (int z = 0; z < newInstructions.Count; ++z) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Installer/CommandSettings.cs b/EXILED/Exiled.Installer/CommandSettings.cs index 55f5b226da..b0ee50d4e2 100644 --- a/EXILED/Exiled.Installer/CommandSettings.cs +++ b/EXILED/Exiled.Installer/CommandSettings.cs @@ -120,6 +120,11 @@ internal sealed class CommandSettings "--get-versions", "Gets all possible versions for installation") { IsRequired = false }, + + new Option( + "--skip-version-select", + "Skips version selecting") + { IsRequired = false }, }; #nullable disable @@ -168,6 +173,11 @@ internal sealed class CommandSettings /// public bool Exit { get; set; } + /// + /// Gets or sets the value indicating whether the version select should be skipped. + /// + public bool SkipVersionSelect { get; set; } + public static async Task Parse(string[] args) { RootCommand.Handler = CommandHandler.Create(async args => await Program.MainSafe(args).ConfigureAwait(false)); diff --git a/EXILED/Exiled.Installer/Program.cs b/EXILED/Exiled.Installer/Program.cs index 80132c7070..1924fc369d 100644 --- a/EXILED/Exiled.Installer/Program.cs +++ b/EXILED/Exiled.Installer/Program.cs @@ -89,7 +89,7 @@ internal static async Task MainSafe(CommandSettings args) GitHubClient.Credentials = new Credentials(args.GitHubToken, AuthenticationType.Bearer); } - if (string.IsNullOrEmpty(args.TargetVersion) && !args.PreReleases) + if (string.IsNullOrEmpty(args.TargetVersion) && !args.PreReleases && !args.SkipVersionSelect) { Console.WriteLine(Resources.Program_MainSafe_Which_version_would_you_like_to_install_); Console.WriteLine(Resources.Program_MainSafe__1___Latest_stable_release); diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 36c845a38d..990fa0ca9f 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, 4); + public static readonly Version RequiredSCPSLVersion = new(14, 2, 0, 0); } } \ No newline at end of file diff --git a/EXILED/Exiled.Loader/LoaderPlugin.cs b/EXILED/Exiled.Loader/LoaderPlugin.cs index b2f5215ccd..019a308f10 100644 --- a/EXILED/Exiled.Loader/LoaderPlugin.cs +++ b/EXILED/Exiled.Loader/LoaderPlugin.cs @@ -13,7 +13,6 @@ namespace Exiled.Loader using LabApi.Loader.Features.Plugins; using LabApi.Loader.Features.Plugins.Enums; - using MEC; using Log = API.Features.Log; @@ -54,7 +53,7 @@ public class LoaderPlugin : Plugin /// /// Gets the RequiredApiVersion of the EXILED Loader. /// - public override Version RequiredApiVersion { get; } = Assembly.GetAssembly(typeof(LabApi.Loader.PluginLoader)).GetReferencedAssemblies()[0].Version; // TODO Not finish + public override Version RequiredApiVersion => LabApi.Features.LabApiProperties.CurrentVersion; /// /// Gets the Exiled Version. diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 748a73f091..5b6089a83a 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.4) +Last Update (14.2.0.0) ### Index @@ -231,6 +231,7 @@ Last Update (14.1.0.4) - [PlayerInfoColorTypes](#playerinfocolortypes) - [PlayerMovementState](#playermovementstate) - [PlayerPermissions](#playerpermissions) +- [PlayerPermissionType](#playerpermissiontype) - [PlayerSorting](#playersorting) - [PopupState](#popupstate) - [PortMapper](#portmapper) @@ -238,6 +239,7 @@ Last Update (14.1.0.4) - [RaClipBoardType](#raclipboardtype) - [RadioCommand](#radiocommand) - [RadioRangeLevel](#radiorangelevel) +- [RefpointType](#refpointtype) - [RejectionReason](#rejectionreason) - [RejectionReason](#rejectionreason) - [ReloaderMessageHeader](#reloadermessageheader) @@ -245,6 +247,7 @@ Last Update (14.1.0.4) - [RemovalMode](#removalmode) - [ReproProjectAssetType](#reproprojectassettype) - [RequestType](#requesttype) +- [ResizeDirection](#resizedirection) - [RespawnSetting](#respawnsetting) - [RespawnTooltipTranslation](#respawntooltiptranslation) - [ResurrectError](#resurrecterror) @@ -315,9 +318,11 @@ Last Update (14.1.0.4) - [SyncData](#syncdata) - [SyncDataFlags](#syncdataflags) - [SyncMode](#syncmode) +- [SystemCursorID](#systemcursorid) - [TargetButton](#targetbutton) - [Team](#team) - [TrackerMessage](#trackermessage) +- [TrackType](#tracktype) - [TransitionStatus](#transitionstatus) - [TurnStatus](#turnstatus) - [UISetting](#uisetting) @@ -928,6 +933,12 @@ Last Update (14.1.0.4) [5] = Green [6] = Blue [7] = Pink + [8] = Orange + [9] = White + [10] = Gray + [11] = Black + [12] = Brown + [13] = Evil ``` @@ -1308,7 +1319,7 @@ Last Update (14.1.0.4) ### ConsoleLogType -
GameCore.Console+ConsoleLogType +
GameCore.ConsoleLogType ``` [0] = DoNotLog @@ -1699,7 +1710,7 @@ Last Update (14.1.0.4)
Interactables.Interobjects.ElevatorGroup ``` - [0] = GateA + [0] = GateA01 [1] = GateB [2] = LczA01 [3] = LczA02 @@ -1709,6 +1720,7 @@ Last Update (14.1.0.4) [7] = Scp049 [8] = Nuke02 [9] = ServerRoom + [10] = GateA02 ```
@@ -2740,6 +2752,7 @@ Last Update (14.1.0.4) [64] = KeycardCustomSite02 [65] = KeycardCustomManagement [66] = KeycardCustomMetalCase + [67] = MarshmallowItem [-1] = None ``` @@ -3125,7 +3138,7 @@ Last Update (14.1.0.4)
UserSettings.VideoSettings.MiscVideoSetting ``` - [0] = Brightness + [0] = BrightnessBoost [1] = ExplosionShake [2] = HeadBobbing [3] = FlashbangDarkMode @@ -3690,6 +3703,18 @@ Last Update (14.1.0.4)
+### PlayerPermissionType + +
PlayerPermissionType + +``` + [0] = RaAccess + [1] = ViewAccess + [2] = Misc +``` + +
+ ### PlayerSorting
RemoteAdmin.Communication.RaPlayerList+PlayerSorting @@ -3777,6 +3802,18 @@ Last Update (14.1.0.4)
+### RefpointType + +
PlayerRoles.FirstPersonControl.Thirdperson.RefpointType + +``` + [0] = None + [1] = LineOfSightPoint + [2] = Hitbox +``` + +
+ ### RejectionReason
RejectionReason @@ -3882,6 +3919,20 @@ Last Update (14.1.0.4)
+### ResizeDirection + +
GameCore.ResizableWindow+ResizeDirection + +``` + [0] = None + [1] = Left + [2] = Right + [3] = Top + [4] = Bottom +``` + +
+ ### RespawnSetting
Respawning.Graphics.RespawnSetting @@ -4047,6 +4098,8 @@ Last Update (14.1.0.4) [36] = Pocket [37] = HczTestroom [38] = Hcz127 + [39] = HczAcroamaticAbatement + [40] = HczWaysideIncinerator ```
@@ -4172,6 +4225,8 @@ Last Update (14.1.0.4) [0] = AttackStart [1] = Hit [2] = Holster + [3] = Evil + [4] = Cackle ```
@@ -5131,6 +5186,7 @@ Last Update (14.1.0.4) [28] = NTFMiniwaveEntrance [29] = ChaosEntrance [30] = ChaosMiniwaveEntrance + [31] = DeadMansSwitch [254] = Custom [255] = None ``` @@ -5178,6 +5234,31 @@ Last Update (14.1.0.4)
+### SystemCursorID + +
GameCore.SystemCursorID + +``` + [32512] = Default + [32513] = TextSelectBeam + [32514] = WaitingCircle + [32515] = Cross + [32516] = UpArrow + [32642] = SizeDiagonalTopBottom + [32643] = SizeDiagonalBottomTop + [32644] = SizeHorizontal + [32645] = SizeVertical + [32646] = SizeAll + [32648] = Block + [32649] = LinkSelect + [32650] = AppStarting + [32651] = Help + [32671] = Pin + [32672] = Person +``` + +
+ ### TargetButton
AdminToys.ShootingTarget+TargetButton @@ -5225,6 +5306,19 @@ Last Update (14.1.0.4)
+### TrackType + +
PlayerRoles.PlayableScps.Scp939.Scp939ChaseThemeProvider+TrackType + +``` + [0] = None + [1] = Quiet + [2] = MadeSound + [3] = Detected +``` + +
+ ### TransitionStatus
PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.InventorySubcontroller+TransitionStatus @@ -5543,6 +5637,7 @@ Last Update (14.1.0.4) [4] = MenuUI [5] = Scp127Voice [6] = Scp3114Voice + [7] = ChaseThemes ```
@@ -5664,7 +5759,7 @@ Last Update (14.1.0.4)
Damage Handlers -```md title="Latest Updated: 14.1.0.4" +```md title="Latest Updated: 14.2.0.0" All available DamageHandlers + Symbol ':' literally means "inherits from" @@ -5685,6 +5780,7 @@ PlayerStatsSystem.Scp018DamageHandler : AttackerDamageHandler PlayerStatsSystem.Scp049DamageHandler : ScpDamageHandler PlayerStatsSystem.Scp096DamageHandler : ScpDamageHandler PlayerStatsSystem.ScpDamageHandler : AttackerDamageHandler +PlayerStatsSystem.SilentDamageHandler : StandardDamageHandler PlayerStatsSystem.UniversalDamageHandler : StandardDamageHandler PlayerStatsSystem.WarheadDamageHandler : StandardDamageHandler PlayerRoles.PlayableScps.Scp939.Scp939DamageHandler : AttackerDamageHandler From 80f3f6a6b46eefe818bb935067b124197acc9c8b Mon Sep 17 00:00:00 2001 From: FUTURE <69786695+FUTURE-SL@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:43:59 +0300 Subject: [PATCH 164/224] fix; switch statement for reservedSlotEventResult (#658) --- .../EventArgs/Player/ReservedSlotsCheckEventArgs.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs index d571f1e451..ebb22cf345 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs @@ -56,7 +56,7 @@ public ReservedSlotEventResult Result get => reservedSlotEventResult; set { - switch (reservedSlotEventResult) + switch (value) { case ReservedSlotEventResult.CanUseReservedSlots or ReservedSlotEventResult.UseBaseGameSystem: IsAllowed = HasReservedSlot; @@ -75,4 +75,4 @@ public ReservedSlotEventResult Result } } } -} \ No newline at end of file +} From ba092f8e0b227c5725ee965c1a84ee927007fca1 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Wed, 22 Oct 2025 04:44:19 -0400 Subject: [PATCH 165/224] feat: Obsolete FpcRole.IsNoclipEnabled and added Player.IsNoclipEnabled (#663) thing --- EXILED/Exiled.API/Features/Player.cs | 9 +++++++++ EXILED/Exiled.API/Features/Roles/FpcRole.cs | 2 ++ 2 files changed, 11 insertions(+) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 80b9844f49..36bd229ba7 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -467,6 +467,15 @@ public bool IsNoclipPermitted } } + /// + /// Gets or sets a value indicating whether the player has noclip enabled. + /// + public bool IsNoclipEnabled + { + get => ReferenceHub.playerStats.GetModule().HasFlag(AdminFlags.Noclip); + set => ReferenceHub.playerStats.GetModule().SetFlag(AdminFlags.Noclip, value); + } + /// /// Gets or sets a value indicating the that currently has the player cuffed. /// diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 42c08b449d..2b0d75cf00 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features.Roles { + using System; using System.Collections.Generic; using Exiled.API.Features.Pools; @@ -268,6 +269,7 @@ public PlayerMovementState MoveState /// indicating status. /// For permitting a player to enter and exit noclip freely, see . /// + [Obsolete("Use Player::IsNoclipEnabled instead")] public bool IsNoclipEnabled { get => Owner.ReferenceHub.playerStats.GetModule().HasFlag(AdminFlags.Noclip); From 1b03acc4f4b6a71cff847b624ff2ea7491c7ab8f Mon Sep 17 00:00:00 2001 From: "Mr. Baguetter" Date: Wed, 22 Oct 2025 03:46:11 -0500 Subject: [PATCH 166/224] feat: New events JailbirdChangedStateEventArgs and JailbirdChangingStateEventArgs (#655) * feat: Add Jailbird WearState Changed/Changing events * fix: doc fix * Renamed JailbirdChangedStateEventArgs and JailbirdChangingStateEventArgs to JailbirdChangedWearStateEventArgs and JailbirdChangingWearStateEventArgs Made JailbirdChangedWearStateEventArgs and JailbirdChangingWearStateEventArgs both IPlayerEvents JailbirdWearState transpiler now uses nameof() instead of hard-coded field names --- .../Item/JailbirdChangedWearStateEventArgs.cs | 60 +++++++ .../JailbirdChangingWearStateEventArgs.cs | 67 ++++++++ EXILED/Exiled.Events/Handlers/Item.cs | 22 +++ .../Patches/Events/Item/JailbirdWearState.cs | 161 ++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs diff --git a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs new file mode 100644 index 0000000000..1dd9abf14e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs @@ -0,0 +1,60 @@ +// ----------------------------------------------------------------------- +// +// 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.Jailbird; + + /// + /// Contains all information when a changes its state. + /// + public class JailbirdChangedWearStateEventArgs : IItemEvent, IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who owns the jailbird. + /// The Jailbird item whose state is changing. + /// The the Jailbird switched to. + /// The current the Jailbird was at. + public JailbirdChangedWearStateEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState) + { + Player = Player.Get(player); + Jailbird = Item.Get(jailbird); + NewWearState = newWearState; + OldWearState = oldWearState; + } + + /// + /// Gets the who owns the . + /// + public Player Player { get; } + + /// + /// Gets the that changed its state. + /// + public Jailbird Jailbird { get; } + + /// + /// Gets the new of the Jailbird. + /// + public JailbirdWearState NewWearState { get; } + + /// + /// Gets the old of the Jailbird. + /// + public JailbirdWearState OldWearState { get; } + + /// + /// Gets the associated with the Jailbird. + /// + public Item Item => Jailbird; + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs new file mode 100644 index 0000000000..560cb80dcc --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs @@ -0,0 +1,67 @@ +// ----------------------------------------------------------------------- +// +// 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.Jailbird; + + /// + /// Contains all information before a changes its state. + /// + public class JailbirdChangingWearStateEventArgs : IItemEvent, IPlayerEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who owns the jailbird. + /// The Jailbird item whose state is changing. + /// Whether the Jailbird's state is allowed to change. + /// The the Jailbird is attempting to switch to. + /// The current the Jailbird is at. + public JailbirdChangingWearStateEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState, bool isAllowed = true) + { + Player = Player.Get(player); + Jailbird = Item.Get(jailbird); + IsAllowed = isAllowed; + NewWearState = newWearState; + OldWearState = oldWearState; + } + + /// + /// Gets the who owns the . + /// + public Player Player { get; } + + /// + /// Gets the that atempted to change its state. + /// + public Jailbird Jailbird { get; } + + /// + /// Gets or sets the new of the Jailbird. + /// + public JailbirdWearState NewWearState { get; set; } + + /// + /// Gets the old of the Jailbird. + /// + public JailbirdWearState OldWearState { get; } + + /// + /// Gets the associated with the Jailbird. + /// + public Item Item => Jailbird; + + /// + /// Gets or sets a value indicating whether the Jailbird is allowed to change its . + /// + public bool IsAllowed { get; set; } + } +} diff --git a/EXILED/Exiled.Events/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index 4020038448..9ac83aafd3 100644 --- a/EXILED/Exiled.Events/Handlers/Item.cs +++ b/EXILED/Exiled.Events/Handlers/Item.cs @@ -79,6 +79,28 @@ public static class Item /// public static Event DisruptorFiring { get; set; } = new(); + /// + /// Invoked before the Jailbird's is changed. + /// + public static Event JailbirdChangingWearState { get; set; } = new(); + + /// + /// Invoked after the Jailbird's is changed. + /// + public static Event JailbirdChangedWearState { get; set; } = new(); + + /// + /// Called before the Jailbird's is changed. + /// + /// The instance. + public static void OnJailbirdStateChanging(JailbirdChangingWearStateEventArgs ev) => JailbirdChangingWearState.InvokeSafely(ev); + + /// + /// Called after the Jailbird's is changed. + /// + /// The instance. + public static void OnJailbirdStateChanged(JailbirdChangedWearStateEventArgs ev) => JailbirdChangedWearState.InvokeSafely(ev); + /// /// Called before a firing while on the ground. /// WARNING: Client still receive the shoot sound AND the ammo is still removed. (even if = false). diff --git a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs new file mode 100644 index 0000000000..942fb27cda --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs @@ -0,0 +1,161 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Item +{ + 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.Jailbird; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add and events. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.JailbirdChangingWearState))] + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.JailbirdChangedWearState))] + [HarmonyPatch(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.RecheckUsage))] + internal static class JailbirdWearState + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder oldState = generator.DeclareLocal(typeof(JailbirdWearState)); + LocalBuilder evChanging = generator.DeclareLocal(typeof(JailbirdChangingWearStateEventArgs)); + + Label skipChangedEventLabel = generator.DefineLabel(); + Label retLabel = generator.DefineLabel(); + + int originalStloc2Index = newInstructions.FindIndex(x => x.opcode == OpCodes.Stloc_2); + if (originalStloc2Index == -1) + { + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + yield break; + } + + CodeInstruction[] prefixInstructions = new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.WearState))), + new(OpCodes.Stloc_S, oldState.LocalIndex), + }; + + newInstructions.InsertRange(0, prefixInstructions); + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + + int targetStloc2Index = originalStloc2Index + prefixInstructions.Length; + + newInstructions.InsertRange( + targetStloc2Index + 1, + new[] + { + // if (oldWearState == newWearState) + // skipChangedEventLabel; + new CodeInstruction(OpCodes.Ldloc_S, oldState.LocalIndex), + new(OpCodes.Ldloc_2), + new(OpCodes.Beq_S, skipChangedEventLabel), + + // this._jailbird.Owner + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdItem), nameof(JailbirdItem.Owner))), + + // this._jailbird + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), + + // newWearState + new(OpCodes.Ldloc_2), + + // oldWearState + new(OpCodes.Ldloc_S, oldState.LocalIndex), + + // true + new(OpCodes.Ldc_I4_1), + + // JailbirdChangingWearStateEventArgs ev = new(...) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangingWearStateEventArgs))[0]), + new(OpCodes.Stloc_S, evChanging.LocalIndex), + + // Handlers.Item.OnJailbirdStateChanging(ev) + new(OpCodes.Ldloc_S, evChanging.LocalIndex), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanging))), + + // if (!IsAllowed) + // return; + new(OpCodes.Ldloc_S, evChanging.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + + // Update local 2 + new(OpCodes.Ldloc_S, evChanging.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.NewWearState))), + new(OpCodes.Stloc_2), + }); + + int dictSetIndex = newInstructions.FindIndex(x => x.Calls(Method(typeof(Dictionary), "set_Item"))); + if (dictSetIndex == -1) + { + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + yield break; + } + + int insertAfterDictSet = dictSetIndex + 1; + + newInstructions.InsertRange( + insertAfterDictSet, + new[] + { + new CodeInstruction(OpCodes.Nop).WithLabels(skipChangedEventLabel), + + // if (oldWearState == newWearState) + // return; + new(OpCodes.Ldloc_S, oldState.LocalIndex), + new(OpCodes.Ldloc_2), + new(OpCodes.Beq_S, retLabel), + + // this._jailbird.Owner + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdItem), nameof(JailbirdItem.Owner))), + + // this._jailbird + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), + + // newWearState + new(OpCodes.Ldloc_2), + + // oldWearState + new(OpCodes.Ldloc_S, oldState.LocalIndex), + + // JailbirdChangedWearStateEventArgs ev = new(...) + // Handlers.Item.OnJailbirdStateChanged(ev) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangedWearStateEventArgs))[0]), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanged))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From 4dda9d99b5b48bbb5699a1c275515cefc49c9dcf Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:58:03 +0200 Subject: [PATCH 167/224] improve :JailbirdWearState Event (Error on Ret) --- .../Item/JailbirdChangedWearStateEventArgs.cs | 5 +- .../JailbirdChangingWearStateEventArgs.cs | 9 +- .../Patches/Events/Item/JailbirdWearState.cs | 177 +++++++----------- 3 files changed, 74 insertions(+), 117 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs index 1dd9abf14e..1bfbcd529e 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangedWearStateEventArgs.cs @@ -20,14 +20,13 @@ public class JailbirdChangedWearStateEventArgs : IItemEvent, IPlayerEvent /// /// Initializes a new instance of the class. /// - /// The player who owns the jailbird. /// The Jailbird item whose state is changing. /// The the Jailbird switched to. /// The current the Jailbird was at. - public JailbirdChangedWearStateEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState) + public JailbirdChangedWearStateEventArgs(InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState) { - Player = Player.Get(player); Jailbird = Item.Get(jailbird); + Player = Jailbird.Owner; NewWearState = newWearState; OldWearState = oldWearState; } diff --git a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs index 560cb80dcc..bf7ac51d95 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/JailbirdChangingWearStateEventArgs.cs @@ -20,16 +20,13 @@ public class JailbirdChangingWearStateEventArgs : IItemEvent, IPlayerEvent, IDen /// /// Initializes a new instance of the class. /// - /// The player who owns the jailbird. /// The Jailbird item whose state is changing. - /// Whether the Jailbird's state is allowed to change. /// The the Jailbird is attempting to switch to. /// The current the Jailbird is at. - public JailbirdChangingWearStateEventArgs(ReferenceHub player, InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState, bool isAllowed = true) + public JailbirdChangingWearStateEventArgs(InventorySystem.Items.ItemBase jailbird, JailbirdWearState newWearState, JailbirdWearState oldWearState) { - Player = Player.Get(player); Jailbird = Item.Get(jailbird); - IsAllowed = isAllowed; + Player = Jailbird.Owner; NewWearState = newWearState; OldWearState = oldWearState; } @@ -62,6 +59,6 @@ public JailbirdChangingWearStateEventArgs(ReferenceHub player, InventorySystem.I /// /// Gets or sets a value indicating whether the Jailbird is allowed to change its . /// - public bool IsAllowed { get; set; } + public bool IsAllowed { get; set; } = true; } } diff --git a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs index 942fb27cda..3416a7e3a2 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs @@ -31,126 +31,87 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - LocalBuilder oldState = generator.DeclareLocal(typeof(JailbirdWearState)); + LocalBuilder oldState = generator.DeclareLocal(typeof(InventorySystem.Items.Jailbird.JailbirdWearState)); LocalBuilder evChanging = generator.DeclareLocal(typeof(JailbirdChangingWearStateEventArgs)); - Label skipChangedEventLabel = generator.DefineLabel(); Label retLabel = generator.DefineLabel(); - int originalStloc2Index = newInstructions.FindIndex(x => x.opcode == OpCodes.Stloc_2); - if (originalStloc2Index == -1) + newInstructions.InsertRange(0, new CodeInstruction[] { - for (int i = 0; i < newInstructions.Count; i++) - yield return newInstructions[i]; + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.WearState))), + new(OpCodes.Stloc_S, oldState.LocalIndex), + }); - ListPool.Pool.Return(newInstructions); - yield break; - } + int offset = 1; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Stloc_2) + offset; - CodeInstruction[] prefixInstructions = new CodeInstruction[] + newInstructions.InsertRange(index, new[] { + // if (oldWearState == newWearState) + // return; + new CodeInstruction(OpCodes.Ldloc_S, oldState.LocalIndex), + new(OpCodes.Ldloc_2), + new(OpCodes.Beq, retLabel), + + // this._jailbird new(OpCodes.Ldarg_0), - new(OpCodes.Call, PropertyGetter(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.WearState))), - new(OpCodes.Stloc_S, oldState.LocalIndex), - }; + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), - newInstructions.InsertRange(0, prefixInstructions); - newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + // newWearState + new(OpCodes.Ldloc_2), + + // oldWearState + new(OpCodes.Ldloc_S, oldState.LocalIndex), + + // JailbirdChangingWearStateEventArgs ev = new(InventorySystem.Items.ItemBase, JailbirdWearState, JailbirdWearState) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangingWearStateEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, evChanging.LocalIndex), + + // Handlers.Item.OnJailbirdStateChanging(ev) + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanging))), - int targetStloc2Index = originalStloc2Index + prefixInstructions.Length; - - newInstructions.InsertRange( - targetStloc2Index + 1, - new[] - { - // if (oldWearState == newWearState) - // skipChangedEventLabel; - new CodeInstruction(OpCodes.Ldloc_S, oldState.LocalIndex), - new(OpCodes.Ldloc_2), - new(OpCodes.Beq_S, skipChangedEventLabel), - - // this._jailbird.Owner - new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), - new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdItem), nameof(JailbirdItem.Owner))), - - // this._jailbird - new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), - - // newWearState - new(OpCodes.Ldloc_2), - - // oldWearState - new(OpCodes.Ldloc_S, oldState.LocalIndex), - - // true - new(OpCodes.Ldc_I4_1), - - // JailbirdChangingWearStateEventArgs ev = new(...) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangingWearStateEventArgs))[0]), - new(OpCodes.Stloc_S, evChanging.LocalIndex), - - // Handlers.Item.OnJailbirdStateChanging(ev) - new(OpCodes.Ldloc_S, evChanging.LocalIndex), - new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanging))), - - // if (!IsAllowed) - // return; - new(OpCodes.Ldloc_S, evChanging.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.IsAllowed))), - new(OpCodes.Brfalse_S, retLabel), - - // Update local 2 - new(OpCodes.Ldloc_S, evChanging.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.NewWearState))), - new(OpCodes.Stloc_2), - }); - - int dictSetIndex = newInstructions.FindIndex(x => x.Calls(Method(typeof(Dictionary), "set_Item"))); - if (dictSetIndex == -1) + // if (!IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + + // newWearState = ev.NewWearState; + new(OpCodes.Ldloc_S, evChanging.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdChangingWearStateEventArgs), nameof(JailbirdChangingWearStateEventArgs.NewWearState))), + new(OpCodes.Stloc_2), + }); + + offset = 1; + index = newInstructions.FindIndex(x => x.Calls(Method(typeof(Dictionary), "set_Item"))); + + newInstructions.InsertRange(index, new CodeInstruction[] { - for (int i = 0; i < newInstructions.Count; i++) - yield return newInstructions[i]; - - ListPool.Pool.Return(newInstructions); - yield break; - } - - int insertAfterDictSet = dictSetIndex + 1; - - newInstructions.InsertRange( - insertAfterDictSet, - new[] - { - new CodeInstruction(OpCodes.Nop).WithLabels(skipChangedEventLabel), - - // if (oldWearState == newWearState) - // return; - new(OpCodes.Ldloc_S, oldState.LocalIndex), - new(OpCodes.Ldloc_2), - new(OpCodes.Beq_S, retLabel), - - // this._jailbird.Owner - new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), - new(OpCodes.Callvirt, PropertyGetter(typeof(JailbirdItem), nameof(JailbirdItem.Owner))), - - // this._jailbird - new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), - - // newWearState - new(OpCodes.Ldloc_2), - - // oldWearState - new(OpCodes.Ldloc_S, oldState.LocalIndex), - - // JailbirdChangedWearStateEventArgs ev = new(...) - // Handlers.Item.OnJailbirdStateChanged(ev) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangedWearStateEventArgs))[0]), - new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanged))), - }); + // if (oldWearState == newWearState) + // return; + new(OpCodes.Ldloc_S, oldState.LocalIndex), + new(OpCodes.Ldloc_2), + new(OpCodes.Beq, retLabel), + + // this._jailbird + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker._jailbird))), + + // newWearState + new(OpCodes.Ldloc_2), + + // oldWearState + new(OpCodes.Ldloc_S, oldState.LocalIndex), + + // JailbirdChangedWearStateEventArgs ev = new(InventorySystem.Items.ItemBase, JailbirdWearState, JailbirdWearState) + // Handlers.Item.OnJailbirdStateChanged(ev) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(JailbirdChangedWearStateEventArgs))[0]), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnJailbirdStateChanged))), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; From 7af8e3fb0ee233ccdc5f0edb8e71135dd9af2c1b Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:10:26 +0200 Subject: [PATCH 168/224] fix: JailbirdWearState IL error --- .../Patches/Events/Item/JailbirdWearState.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs index 3416a7e3a2..cd4499e115 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/JailbirdWearState.cs @@ -38,6 +38,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Stloc_2) + offset; - newInstructions.InsertRange(index, new[] + newInstructions.InsertRange(index, new CodeInstruction[] { // if (oldWearState == newWearState) // return; - new CodeInstruction(OpCodes.Ldloc_S, oldState.LocalIndex), + new(OpCodes.Ldloc_S, oldState.LocalIndex), new(OpCodes.Ldloc_2), new(OpCodes.Beq, retLabel), @@ -89,12 +90,6 @@ private static IEnumerable Transpiler(IEnumerable Date: Wed, 22 Oct 2025 20:31:24 +0200 Subject: [PATCH 169/224] Obsolete: CameraType.HczWarheadTop --- EXILED/Exiled.API/Enums/CameraType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EXILED/Exiled.API/Enums/CameraType.cs b/EXILED/Exiled.API/Enums/CameraType.cs index 4b5a3a771f..535e7366ce 100644 --- a/EXILED/Exiled.API/Enums/CameraType.cs +++ b/EXILED/Exiled.API/Enums/CameraType.cs @@ -77,6 +77,7 @@ public enum CameraType HczWarheadArmory, HczWarheadControl, HczWarheadHallway, + [System.Obsolete("This Camera no longer exist.")] HczWarheadTop, #endregion #region Lcz From f2b3005e921179ccbc88947c5c1524b9abc0535b Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:52:42 +0200 Subject: [PATCH 170/224] GlassType.Incinerator & CameraType.HczWarheadTop are not real --- EXILED/Exiled.API/Enums/GlassType.cs | 5 ----- EXILED/Exiled.API/Features/Camera.cs | 1 - EXILED/Exiled.API/Features/Window.cs | 1 - 3 files changed, 7 deletions(-) diff --git a/EXILED/Exiled.API/Enums/GlassType.cs b/EXILED/Exiled.API/Enums/GlassType.cs index a1822d676e..85c068108f 100644 --- a/EXILED/Exiled.API/Enums/GlassType.cs +++ b/EXILED/Exiled.API/Enums/GlassType.cs @@ -84,10 +84,5 @@ public enum GlassType /// Represents the window in . /// GateAArmory, - - /// - /// Represents the window in . - /// - Incinerator, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index dc3247a7b3..fca5cbbc73 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -81,7 +81,6 @@ public class Camera : IWrapper, IWorldSpace ["WARHEAD ARMORY"] = CameraType.HczWarheadArmory, ["WARHEAD CONTROL"] = CameraType.HczWarheadControl, ["WARHEAD HALLWAY"] = CameraType.HczWarheadHallway, - ["WARHEAD TOP"] = CameraType.HczWarheadTop, // Light Containment Zone ["173 BOTTOM"] = CameraType.Lcz173Bottom, diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index 01e1c698e4..e5a1624e0d 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -248,7 +248,6 @@ public void DamageWindow(float amount, DamageHandlerBase handler) _ => GlassType.Unknown, }, "glass" => GlassType.Scp079, - "HCZ_IncineratorWayside" => GlassType.Incinerator, "VTGLASS" => GlassType.Plants, _ => GlassType.Unknown, }; From 8058e172be451d49b323fc7f504af61b55efb7d5 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:38:44 +0200 Subject: [PATCH 171/224] Fix: HczDss08 and HczCrossRoomWater is better for Config --- EXILED/Exiled.API/Enums/RoomType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Enums/RoomType.cs b/EXILED/Exiled.API/Enums/RoomType.cs index 7333ab04dd..5133407188 100644 --- a/EXILED/Exiled.API/Enums/RoomType.cs +++ b/EXILED/Exiled.API/Enums/RoomType.cs @@ -292,12 +292,12 @@ public enum RoomType /// /// Heavy Containment Zone's cross room with waterfall. /// - HczCrossRoomWater, + HczDss08, /// /// Heavy Containment Zone's cross room with waterfall. /// - Dss08 = HczCrossRoomWater, + HczCrossRoomWater = HczDss08, /// /// Heavy Containment Zone's corner. From 0f874c03535f6613a0237a8845b5e951bd628db4 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:32:30 +0200 Subject: [PATCH 172/224] revert: "fix: Player.Count" --- EXILED/Exiled.API/Features/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 36bd229ba7..127406541b 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -150,7 +150,7 @@ public Player(GameObject gameObject) /// /// /// - public static int Count => global::ReferenceHub.GetPlayerCount(CentralAuth.ClientInstanceMode.ReadyClient, CentralAuth.ClientInstanceMode.Host); + public static int Count => Player.List.Count; /// /// Gets a containing cached and their user ids. From d944887cab362a998f7aaadfdd322940b3b6ae7b Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:35:10 +0200 Subject: [PATCH 173/224] PlayerConnectedCount --- EXILED/Exiled.API/Features/Player.cs | 10 ++++++++-- EXILED/Exiled.API/Features/Server.cs | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 127406541b..cba7d0864d 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -146,11 +146,17 @@ public Player(GameObject gameObject) public static IEnumerable Enumerable => Dictionary.Values; /// - /// Gets the number of players currently on the server. + /// Gets the number of players (Count Dummy with it) currently on the server. /// /// /// - public static int Count => Player.List.Count; + /// + public static int Count => List.Count; + + /// + /// Gets the number of connected players currently on the server. + /// + public static int ConnectedCount => ReferenceHub.GetPlayerCount(CentralAuth.ClientInstanceMode.ReadyClient); /// /// Gets a containing cached and their user ids. diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 64b1287959..96b8311fdf 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -146,6 +146,9 @@ public static bool FriendlyFire /// public static int PlayerCount => Player.Count; + /// + public static int PlayerConnectedCount => Player.ConnectedCount; + /// /// Gets or sets the maximum number of players able to be on the server. /// From 0fe143caac121354ac28d1349b9f0051b9b55a8c Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:16:17 +0300 Subject: [PATCH 174/224] fix: Again fix for GenericDamageHandler (#665) * fix: some damage-handler tweaks * feat: add DamageType.Silent --- EXILED/Exiled.API/Enums/DamageType.cs | 5 + .../Features/DamageHandlers/DamageHandler.cs | 18 ++- .../DamageHandlers/DamageHandlerBase.cs | 144 +++++++++++------- .../DamageHandlers/GenericDamageHandler.cs | 30 +++- 4 files changed, 131 insertions(+), 66 deletions(-) diff --git a/EXILED/Exiled.API/Enums/DamageType.cs b/EXILED/Exiled.API/Enums/DamageType.cs index e1054e1f33..9826dc2fba 100644 --- a/EXILED/Exiled.API/Enums/DamageType.cs +++ b/EXILED/Exiled.API/Enums/DamageType.cs @@ -273,5 +273,10 @@ public enum DamageType /// Damage caused by . /// Scp127, + + /// + /// Damage type for . + /// + Silent, } } diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandler.cs index 0fb82947bf..1103ed138e 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandler.cs @@ -50,9 +50,23 @@ public DamageHandler(Player target, BaseHandler baseHandler) : base(baseHandler) { Target = target; - Attacker = baseHandler is PlayerStatsSystem.AttackerDamageHandler handler ? Player.Get(handler.Attacker.Hub) : null; TargetFootprint = target?.Footprint ?? default; - AttackerFootprint = baseHandler is PlayerStatsSystem.AttackerDamageHandler handle ? handle.Attacker : Attacker?.Footprint ?? default; + + if (baseHandler is PlayerStatsSystem.AttackerDamageHandler attackerDamageHandler) + { + Attacker = Player.Get(attackerDamageHandler.Attacker.Hub); + AttackerFootprint = attackerDamageHandler.Attacker; + } + else if (baseHandler is GenericDamageHandler genericDamageHandler) + { + Attacker = Player.Get(genericDamageHandler.Attacker.Hub); + AttackerFootprint = genericDamageHandler.Attacker; + } + else + { + Attacker = null; + AttackerFootprint = default; + } } /// diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs index 6e8a47ba5f..665adc483c 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs @@ -13,12 +13,11 @@ namespace Exiled.API.Features.DamageHandlers using System.Linq; using Enums; - + using Exiled.API.Features.Items; using Extensions; using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.PlayableScps.Scp3114; using PlayerRoles.PlayableScps.Scp939; - using PlayerStatsSystem; using BaseHandler = PlayerStatsSystem.DamageHandlerBase; @@ -97,61 +96,8 @@ public virtual DamageType Type if (damageType != DamageType.Unknown) return damageType; - switch (Base) - { - case CustomReasonDamageHandler: - return DamageType.Custom; - case WarheadDamageHandler: - return DamageType.Warhead; - case ExplosionDamageHandler: - return DamageType.Explosion; - case Scp018DamageHandler: - return DamageType.Scp018; - case RecontainmentDamageHandler: - return DamageType.Recontainment; - case MicroHidDamageHandler: - return DamageType.MicroHid; - case DisruptorDamageHandler: - return DamageType.ParticleDisruptor; - case Scp939DamageHandler: - return DamageType.Scp939; - case JailbirdDamageHandler: - return DamageType.Jailbird; - case Scp1507DamageHandler: - return DamageType.Scp1507; - case Scp956DamageHandler: - return DamageType.Scp956; - case SnowballDamageHandler: - return DamageType.SnowBall; - case Scp3114DamageHandler scp3114DamageHandler: - return scp3114DamageHandler.Subtype switch - { - Scp3114DamageHandler.HandlerType.Strangulation => DamageType.Strangled, - Scp3114DamageHandler.HandlerType.SkinSteal => DamageType.Scp3114, - Scp3114DamageHandler.HandlerType.Slap => DamageType.Scp3114, - _ => DamageType.Unknown, - }; - case Scp049DamageHandler scp049DamageHandler: - return scp049DamageHandler.DamageSubType switch - { - Scp049DamageHandler.AttackType.CardiacArrest => DamageType.CardiacArrest, - Scp049DamageHandler.AttackType.Instakill => DamageType.Scp049, - Scp049DamageHandler.AttackType.Scp0492 => DamageType.Scp0492, - _ => DamageType.Unknown, - }; - case UniversalDamageHandler universal: - { - DeathTranslation translation = DeathTranslations.TranslationsById[universal.TranslationId]; - - if (DamageTypeExtensions.TranslationIdConversion.ContainsKey(translation.Id)) - return DamageTypeExtensions.TranslationIdConversion[translation.Id]; - - Log.Warn($"{nameof(DamageHandler)}.{nameof(Type)}: No matching {nameof(DamageType)} for {nameof(UniversalDamageHandler)} with ID {translation.Id}, type will be reported as {DamageType.Unknown}. Report this to EXILED Devs."); - break; - } - } - - return DamageType.Unknown; + damageType = GetDamageType(); + return damageType; } protected set @@ -241,6 +187,90 @@ public bool BaseIs(out T param) return true; } + /// + /// Gets the assosiated with . + /// + /// from which should be get. If null, will be used. + /// Assosiated . + protected DamageType GetDamageType(BaseHandler damageHandler = null) + { + damageHandler ??= Base; + + switch (damageHandler) + { + case GenericDamageHandler genericDamageHandler: + return GetDamageType(genericDamageHandler.Base); + case CustomReasonDamageHandler: + return DamageType.Custom; + case WarheadDamageHandler: + return DamageType.Warhead; + case ExplosionDamageHandler: + return DamageType.Explosion; + case Scp018DamageHandler: + return DamageType.Scp018; + case RecontainmentDamageHandler: + return DamageType.Recontainment; + case MicroHidDamageHandler: + return DamageType.MicroHid; + case DisruptorDamageHandler: + return DamageType.ParticleDisruptor; + case Scp939DamageHandler: + return DamageType.Scp939; + case JailbirdDamageHandler: + return DamageType.Jailbird; + case Scp1507DamageHandler: + return DamageType.Scp1507; + case Scp956DamageHandler: + return DamageType.Scp956; + case SnowballDamageHandler: + return DamageType.SnowBall; + case Scp3114DamageHandler scp3114DamageHandler: + return scp3114DamageHandler.Subtype switch + { + Scp3114DamageHandler.HandlerType.Strangulation => DamageType.Strangled, + Scp3114DamageHandler.HandlerType.SkinSteal => DamageType.Scp3114, + Scp3114DamageHandler.HandlerType.Slap => DamageType.Scp3114, + _ => DamageType.Unknown, + }; + case Scp049DamageHandler scp049DamageHandler: + return scp049DamageHandler.DamageSubType switch + { + Scp049DamageHandler.AttackType.CardiacArrest => DamageType.CardiacArrest, + Scp049DamageHandler.AttackType.Instakill => DamageType.Scp049, + Scp049DamageHandler.AttackType.Scp0492 => DamageType.Scp0492, + _ => DamageType.Unknown, + }; + case UniversalDamageHandler universal: + DeathTranslation translation = DeathTranslations.TranslationsById[universal.TranslationId]; + + if (DamageTypeExtensions.TranslationIdConversion.ContainsKey(translation.Id)) + return DamageTypeExtensions.TranslationIdConversion[translation.Id]; + + Log.Warn($"{nameof(DamageHandler)}.{nameof(Type)}: No matching {nameof(DamageType)} for {nameof(UniversalDamageHandler)} with ID {translation.Id}, type will be reported as {DamageType.Unknown}. Report this to EXILED Devs."); + break; + case PlayerStatsSystem.FirearmDamageHandler firearmDamageHandler: + return Item.Get(firearmDamageHandler.Firearm).FirearmType switch + { + FirearmType.A7 => DamageType.A7, + FirearmType.Com15 => DamageType.Com15, + FirearmType.Com18 => DamageType.Com18, + FirearmType.Crossvec => DamageType.Crossvec, + FirearmType.Logicer => DamageType.Logicer, + FirearmType.Revolver => DamageType.Revolver, + FirearmType.Scp127 => DamageType.Scp127, + FirearmType.Shotgun => DamageType.Shotgun, + FirearmType.AK => DamageType.AK, + FirearmType.ParticleDisruptor => DamageType.ParticleDisruptor, + FirearmType.E11SR => DamageType.E11Sr, + FirearmType.FSP9 => DamageType.Fsp9, + FirearmType.FRMG0 => DamageType.Frmg0, + _ => DamageType.Firearm + }; + } + + return DamageType.Unknown; + } + /// /// A wrapper to easily manipulate the behavior of . /// diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index c8c5917241..a6092c39c1 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -10,14 +10,13 @@ namespace Exiled.API.Features.DamageHandlers using System; using Enums; - using Footprinting; using Items; - using PlayerRoles; using PlayerRoles.PlayableScps.Scp096; + using PlayerRoles.PlayableScps.Scp1507; + using PlayerRoles.PlayableScps.Scp3114; using PlayerRoles.PlayableScps.Scp939; - using PlayerStatsSystem; using UnityEngine; @@ -123,6 +122,7 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage Base = new ExplosionDamageHandler(attacker.Footprint, UnityEngine.Vector3.zero, damage, 0, ExplosionType.Grenade); break; case DamageType.Firearm: + case DamageType.AK: GenericFirearm(player, attacker, damage, damageType, ItemType.GunAK); break; case DamageType.Crossvec: @@ -137,9 +137,6 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage case DamageType.Shotgun: GenericFirearm(player, attacker, damage, damageType, ItemType.GunShotgun); break; - case DamageType.AK: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunAK); - break; case DamageType.Com15: GenericFirearm(player, attacker, damage, damageType, ItemType.GunCOM15); break; @@ -183,7 +180,7 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage Base = new Scp939DamageHandler(curr939, damage, Scp939DamageType.LungeTarget); break; - case DamageType.Scp: + case DamageType.Scp: // TODO replace ScpDamageHandler with specific SCP-Role damage handler Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Unknown); break; case DamageType.Scp018: @@ -204,8 +201,27 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage case DamageType.Scp106: Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.PocketDecay); break; + case DamageType.CardiacArrest: + Base = new Scp049DamageHandler(attacker.ReferenceHub, damage, Scp049DamageHandler.AttackType.CardiacArrest); + break; + case DamageType.Scp3114: + Base = new Scp3114DamageHandler(attacker.ReferenceHub, damage, Scp3114DamageHandler.HandlerType.Slap); + break; + case DamageType.Strangled: + Base = new Scp3114DamageHandler(attacker.ReferenceHub, damage, Scp3114DamageHandler.HandlerType.Strangulation); + break; + case DamageType.Scp1507: + Base = new Scp1507DamageHandler(attacker.Footprint, damage); + break; + case DamageType.Scp956: + Base = new Scp956DamageHandler(Vector3.forward); + break; + case DamageType.SnowBall: + Base = new SnowballDamageHandler(attacker.Footprint, damage, Vector3.forward); + break; case DamageType.Custom: case DamageType.Unknown: + case DamageType.Marshmallow: default: Base = new CustomReasonDamageHandler(damageText ?? genericDamageText, damage, cassieAnnouncement.Announcement); break; From e6c9ba841221d5aaef74499d5d1f9293de538f14 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:56:43 +0200 Subject: [PATCH 175/224] Scp1509 and new DamageType --- EXILED/Exiled.API/Enums/DamageType.cs | 10 ++ EXILED/Exiled.API/Enums/EffectType.cs | 29 +++--- EXILED/Exiled.API/Enums/SpawnReason.cs | 5 + .../Extensions/DamageTypeExtensions.cs | 10 +- .../Exiled.API/Extensions/ItemExtensions.cs | 2 +- .../DamageHandlers/DamageHandlerBase.cs | 5 + EXILED/Exiled.API/Features/Items/Item.cs | 2 +- EXILED/Exiled.API/Features/Items/Scp1509.cs | 89 ++++++++++++++++++ .../Features/Pickups/Scp1509Pickup.cs | 93 +++++++++++++++++++ .../SCPSLRessources/NW_Documentation.md | 66 ++++++++++--- 10 files changed, 281 insertions(+), 30 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Items/Scp1509.cs create mode 100644 EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs diff --git a/EXILED/Exiled.API/Enums/DamageType.cs b/EXILED/Exiled.API/Enums/DamageType.cs index 9826dc2fba..bb6f2524d5 100644 --- a/EXILED/Exiled.API/Enums/DamageType.cs +++ b/EXILED/Exiled.API/Enums/DamageType.cs @@ -278,5 +278,15 @@ public enum DamageType /// Damage type for . /// Silent, + + /// + /// Damage type for . + /// + GrayCandy, + + /// + /// Damage caused by . + /// + Scp1509, } } diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index 2121e5a591..e70e273e77 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -317,73 +317,78 @@ public enum EffectType /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] Metal, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] OrangeCandy, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] OrangeWitness, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] Prismatic, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] SlowMetabolism, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] Spicy, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] SugarCrave, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] SugarHigh, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] SugarRush, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] TemporaryBypass, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] TraumatizedByEvil, /// /// . /// - [Obsolete("Only availaible for Halloween.")] + // [Obsolete("Only availaible for Halloween.")] WhiteCandy, + + /// + /// . + /// + Scp1509Resurrected, } } diff --git a/EXILED/Exiled.API/Enums/SpawnReason.cs b/EXILED/Exiled.API/Enums/SpawnReason.cs index a1849ba8a6..04660c37ca 100644 --- a/EXILED/Exiled.API/Enums/SpawnReason.cs +++ b/EXILED/Exiled.API/Enums/SpawnReason.cs @@ -66,5 +66,10 @@ public enum SpawnReason : byte // TOTO: Remove this file and use Basegame /// The user has been spawn by the usage of an Item. /// ItemUsage, + + /// + /// The user has been resurected. + /// + Resurrected, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index eaba7fe2bd..3420f2053b 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -7,14 +7,13 @@ namespace Exiled.API.Extensions { - using System.Collections.Generic; - using Enums; - using Features; + using InventorySystem.Items.Scp1509; using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.PlayableScps.Scp3114; using PlayerStatsSystem; + using System.Collections.Generic; /// /// A set of extensions for . @@ -51,6 +50,7 @@ public static class DamageTypeExtensions { DeathTranslations.Hypothermia.Id, DamageType.Hypothermia }, { DeathTranslations.MarshmallowMan.Id, DamageType.Marshmallow }, { DeathTranslations.Scp1344.Id, DamageType.SeveredEyes }, + { DeathTranslations.Scp1509.Id, DamageType.Scp1509 }, }; private static readonly Dictionary TranslationConversionInternal = new() @@ -188,6 +188,10 @@ public static DamageType GetDamageType(DamageHandlerBase damageHandlerBase) return DamageType.Scp956; case SnowballDamageHandler: return DamageType.SnowBall; + case GrayCandyDamageHandler: + return DamageType.GrayCandy; + case Scp1509DamageHandler: + return DamageType.Scp1509; case Scp049DamageHandler scp049DamageHandler: return scp049DamageHandler.DamageSubType switch { diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs index 348f7e635f..80c77a4c37 100644 --- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs +++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs @@ -40,7 +40,7 @@ public static class ItemExtensions /// The item to be checked. /// 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 checkNonFirearm = true) => type.GetFirearmType() is not FirearmType.None || (checkNonFirearm && type is ItemType.MicroHID or ItemType.Jailbird); + public static bool IsWeapon(this ItemType type, bool checkNonFirearm = true) => type.GetFirearmType() is not FirearmType.None || (checkNonFirearm && type is ItemType.MicroHID or ItemType.Jailbird or ItemType.SCP1509); /// /// Check if an item is an SCP. diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs index 665adc483c..ec59d5ee5a 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Features.DamageHandlers using Enums; using Exiled.API.Features.Items; using Extensions; + using InventorySystem.Items.Scp1509; using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.PlayableScps.Scp3114; using PlayerRoles.PlayableScps.Scp939; @@ -224,6 +225,10 @@ protected DamageType GetDamageType(BaseHandler damageHandler = null) return DamageType.Scp956; case SnowballDamageHandler: return DamageType.SnowBall; + case GrayCandyDamageHandler: + return DamageType.GrayCandy; + case Scp1509DamageHandler: + return DamageType.Scp1509; case Scp3114DamageHandler scp3114DamageHandler: return scp3114DamageHandler.Subtype switch { diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 01c71eec71..34906170de 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -166,7 +166,7 @@ public ushort Serial /// /// Gets a value indicating whether this item is a weapon. /// - public bool IsWeapon => this is Firearm || Type is ItemType.Jailbird or ItemType.MicroHID; + public bool IsWeapon => this is Firearm || Type is ItemType.Jailbird or ItemType.MicroHID or ItemType.SCP1509; /// /// Gets a value indicating whether or not this item is a firearm. diff --git a/EXILED/Exiled.API/Features/Items/Scp1509.cs b/EXILED/Exiled.API/Features/Items/Scp1509.cs new file mode 100644 index 0000000000..87c7837fb7 --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Scp1509.cs @@ -0,0 +1,89 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items +{ + using Exiled.API.Interfaces; + using InventorySystem.Items.Scp1509; + + /// + /// A wrapper class for . + /// + public class Scp1509 : Item, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The base class. + public Scp1509(Scp1509Item itemBase) + : base(itemBase) + { + Base = itemBase; + } + + /// + /// Initializes a new instance of the class. + /// + internal Scp1509() + : this((Scp1509Item)Server.Host.Inventory.CreateItemInstance(new(ItemType.SCP1509, 0), false)) + { + } + + /// + /// Gets the that this class is encapsulating. + /// + public new Scp1509Item Base { get; } + + /// + /// Gets or sets the shield regeneration rate. + /// + public float ShieldRegenRate + { + get => Base.ShieldRegenRate; + set => Base.ShieldRegenRate = value; + } + + /// + /// Gets or sets the shield decay rate. + /// + public float ShieldDecayRate + { + get => Base.ShieldDecayRate; + set => Base.ShieldDecayRate = value; + } + + /// + /// Gets or sets the shield time pause when player get damage. + /// + public float ShieldOnDamagePause + { + get => Base.ShieldOnDamagePause; + set => Base.ShieldOnDamagePause = value; + } + + /// + /// Gets or sets the delay after the decay start. + /// + public float UnequipDecayDelay + { + get => Base.UnequipDecayDelay; + set => Base.UnequipDecayDelay = value; + } + + /// + /// Clones current object. + /// + /// New object. + public override Item Clone() => new Scp1509() + { + ShieldRegenRate = ShieldRegenRate, + ShieldDecayRate = ShieldDecayRate, + ShieldOnDamagePause = ShieldOnDamagePause, + UnequipDecayDelay = UnequipDecayDelay, + }; + } +} diff --git a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs new file mode 100644 index 0000000000..32b53550b4 --- /dev/null +++ b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs @@ -0,0 +1,93 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Pickups +{ + using Exiled.API.Enums; + using Exiled.API.Features.Items; + using Exiled.API.Features.Items.Keycards; + using Exiled.API.Interfaces; + + using BaseScp1509 = InventorySystem.Items.Scp1509.Scp1509Pickup; + + /// + /// A wrapper class for a Radio pickup. + /// + public class Scp1509Pickup : Pickup, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The base class. + internal Scp1509Pickup(BaseScp1509 pickupBase) + : base(pickupBase) + { + Base = pickupBase; + } + + /// + /// Initializes a new instance of the class. + /// + internal Scp1509Pickup() + : base(ItemType.Radio) + { + Base = (BaseScp1509)((Pickup)this).Base; + } + + /// + /// Gets the that this class is encapsulating. + /// + public new BaseScp1509 Base { get; } + + /// + /// Gets or sets the shield regeneration rate. + /// + public float ShieldRegenRate { get; set; } + + /// + /// Gets or sets the shield decay rate. + /// + public float ShieldDecayRate { get; set; } + + /// + /// Gets or sets the shield time pause when player get damage. + /// + public float ShieldOnDamagePause { get; set; } + + /// + /// Gets or sets the delay after the decay start. + /// + public float UnequipDecayDelay { get; set; } + + + /// + internal override void ReadItemInfo(Item item) + { + base.ReadItemInfo(item); + if (item is Scp1509 scp1509item) + { + Permissions = scp1509item.Permissions; + } + } + + /// + protected override void InitializeProperties(ItemBase itemBase) + { + base.InitializeProperties(itemBase); + if (itemBase is Scp1509Pickup scp1509Pickup) + { + + } + } + + /// + /// Returns the RadioPickup in a human readable format. + /// + /// A string containing RadioPickup related data. + public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{BatteryLevel}| -{Range}- /{IsEnabled}/"; + } +} diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 5b6089a83a..f9be26f3be 100644 --- a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md +++ b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md @@ -155,6 +155,7 @@ Last Update (14.2.0.0) - [HintTranslations](#hinttranslations) - [HintType](#hinttype) - [HitboxType](#hitboxtype) +- [HitResult](#hitresult) - [HolidayType](#holidaytype) - [HotkeysTranslation](#hotkeystranslation) - [IcomText](#icomtext) @@ -282,9 +283,11 @@ Last Update (14.2.0.0) - [Scp127Tier](#scp127tier) - [Scp127VoiceLinesTranslation](#scp127voicelinestranslation) - [Scp1344Status](#scp1344status) +- [Scp1509MessageType](#scp1509messagetype) - [Scp173SoundId](#scp173soundid) - [Scp244State](#scp244state) - [Scp3114HudTranslation](#scp3114hudtranslation) +- [Scp3114MaterialType](#scp3114materialtype) - [Scp914InteractCode](#scp914interactcode) - [Scp914KnobSetting](#scp914knobsetting) - [Scp914Mode](#scp914mode) @@ -337,7 +340,6 @@ Last Update (14.2.0.0) - [ValidationError](#validationerror) - [ValidationError](#validationerror) - [ValidationError](#validationerror) -- [VariantType](#varianttype) - [VcMuteFlags](#vcmuteflags) - [VcPrivacyFlags](#vcprivacyflags) - [VersionType](#versiontype) @@ -2408,6 +2410,19 @@ Last Update (14.2.0.0)
+### HitResult + +
InventorySystem.Items.Scp1509.Scp1509Hitreg+HitResult + +``` + [0] = HitPlayer + [1] = KilledPlayer + [2] = HitWall + [3] = Missed +``` + +
+ ### HolidayType
MapGeneration.Holidays.HolidayType @@ -2753,6 +2768,7 @@ Last Update (14.2.0.0) [65] = KeycardCustomManagement [66] = KeycardCustomMetalCase [67] = MarshmallowItem + [68] = SCP1509 [-1] = None ``` @@ -3987,6 +4003,7 @@ Last Update (14.2.0.0) [8] = Destroyed [9] = RespawnMiniwave [10] = ItemUsage + [11] = Resurrected ```
@@ -4681,6 +4698,27 @@ Last Update (14.2.0.0) +### Scp1509MessageType + +
InventorySystem.Items.Scp1509.Scp1509MessageType + +``` + [0] = Holstered + [1] = AttackTriggered + [2] = AttackPreformedHitWall + [3] = AttackPreformedHitFlesh + [4] = AttackPreformedHitMissed + [5] = SpawnResurrectParticles + [6] = AttackPerformed + [7] = Inspect + [8] = CanResurrect + [9] = CanNotResurrect + [10] = KilledPlayer + [11] = NewPlayerFullResync +``` + +
+ ### Scp173SoundId
PlayerRoles.PlayableScps.Scp173.Scp173AudioPlayer+Scp173SoundId @@ -4731,6 +4769,18 @@ Last Update (14.2.0.0)
+### Scp3114MaterialType + +
PlayerRoles.PlayableScps.Scp3114.Scp3114MaterialType + +``` + [0] = Original + [1] = Disguise + [2] = Reveal +``` + +
+ ### Scp914InteractCode
Scp914.Scp914InteractCode @@ -5507,18 +5557,6 @@ Last Update (14.2.0.0)
-### VariantType - -
PlayerRoles.PlayableScps.Scp3114.Scp3114FakeModelManager+VariantType - -``` - [0] = Original - [1] = Disguise - [2] = Reveal -``` - -
- ### VcMuteFlags
VoiceChat.VcMuteFlags @@ -5773,6 +5811,7 @@ PlayerStatsSystem.CustomReasonFirearmDamageHandler : FirearmDamageHandler PlayerStatsSystem.DisruptorDamageHandler : AttackerDamageHandler PlayerStatsSystem.ExplosionDamageHandler : AttackerDamageHandler PlayerStatsSystem.FirearmDamageHandler : AttackerDamageHandler +PlayerStatsSystem.GrayCandyDamageHandler : AttackerDamageHandler PlayerStatsSystem.JailbirdDamageHandler : AttackerDamageHandler PlayerStatsSystem.MicroHidDamageHandler : AttackerDamageHandler PlayerStatsSystem.RecontainmentDamageHandler : AttackerDamageHandler @@ -5786,6 +5825,7 @@ PlayerStatsSystem.WarheadDamageHandler : StandardDamageHandler PlayerRoles.PlayableScps.Scp939.Scp939DamageHandler : AttackerDamageHandler PlayerRoles.PlayableScps.Scp3114.Scp3114DamageHandler : AttackerDamageHandler PlayerRoles.PlayableScps.Scp1507.Scp1507DamageHandler : AttackerDamageHandler +InventorySystem.Items.Scp1509.Scp1509DamageHandler : AttackerDamageHandler ```
\ No newline at end of file From f8a199834a90760810fb814579c793f3e8f67bc8 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:14:54 +0200 Subject: [PATCH 176/224] Update GenericDamageHandler for new DamgeType --- .../Features/DamageHandlers/GenericDamageHandler.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index a6092c39c1..d55324798e 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -10,7 +10,10 @@ namespace Exiled.API.Features.DamageHandlers using System; using Enums; + using Exiled.API.Features.Pickups.Projectiles; + using Footprinting; + using InventorySystem.Items.Scp1509; using Items; using PlayerRoles; using PlayerRoles.PlayableScps.Scp096; @@ -113,6 +116,12 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage case DamageType.Jailbird: Base = new JailbirdDamageHandler(Attacker.Hub, damage, Vector3.zero); break; + case DamageType.Scp1509: + Base = new Scp1509DamageHandler(Attacker.Hub, damage, Vector3.zero); + break; + case DamageType.GrayCandy: + Base = new GrayCandyDamageHandler(Attacker.Hub, damage); + break; case DamageType.MicroHid: InventorySystem.Items.MicroHID.MicroHIDItem microHidOwner = new(); microHidOwner.Owner = attacker.ReferenceHub; @@ -184,7 +193,9 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Unknown); break; case DamageType.Scp018: - Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Unknown); + Scp018Projectile scp018Projectile = Projectile.Create(ProjectileType.Scp018); + scp018Projectile.PreviousOwner = attacker; + Base = new Scp018DamageHandler(scp018Projectile.Base, damage, true); break; case DamageType.Scp207: Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Scp207); From 3bdc0eb59a5a64158d57c309380be232e1f627f7 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:39:29 +0200 Subject: [PATCH 177/224] 9.10.0-rc.2 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index bfecd8b8fe..b8f3cb4262 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.0-rc.1 + 9.10.0-rc.2 false From 22bbc06cf9294a1ece8724c5344bf320e054cb29 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:00:42 +0200 Subject: [PATCH 178/224] Fix missing implementation for Item and Pickup --- EXILED/Exiled.API/Features/Items/Item.cs | 3 +++ EXILED/Exiled.API/Features/Pickups/Pickup.cs | 3 +++ EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 34906170de..f9d8a74a0d 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -25,6 +25,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.MicroHID; using InventorySystem.Items.Pickups; using InventorySystem.Items.Radio; + using InventorySystem.Items.Scp1509; using InventorySystem.Items.ThrowableProjectiles; using InventorySystem.Items.ToggleableLights; using InventorySystem.Items.Usables; @@ -254,6 +255,7 @@ public static Item Get(ItemBase itemBase) Scp018Projectile => new Scp018(throwable), _ => new Throwable(throwable), }, + Scp1509Item scp1509 => new Scp1509(scp1509), _ => new(itemBase), }; } @@ -353,6 +355,7 @@ public static T Get(ushort serial) Scp018Projectile => new Scp018(type, owner), _ => new Throwable(type, owner), }, + Scp1509Item => new Scp1509(), _ => new(type), }; diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index 3a7011c2ec..9894b43277 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -35,6 +35,7 @@ namespace Exiled.API.Features.Pickups using BaseMicroHIDPickup = InventorySystem.Items.MicroHID.MicroHIDPickup; using BaseRadioPickup = InventorySystem.Items.Radio.RadioPickup; using BaseScp018Projectile = InventorySystem.Items.ThrowableProjectiles.Scp018Projectile; + using BaseScp1509Pickup = InventorySystem.Items.Scp1509.Scp1509Pickup; using BaseScp1576Pickup = InventorySystem.Items.Usables.Scp1576.Scp1576Pickup; using BaseScp2176Projectile = InventorySystem.Items.ThrowableProjectiles.Scp2176Projectile; using BaseScp330Pickup = InventorySystem.Items.Usables.Scp330.Scp330Pickup; @@ -342,6 +343,7 @@ public static Pickup Get(ItemPickupBase pickupBase) TimeGrenade timeGrenade => new TimeGrenadeProjectile(timeGrenade), _ => new Projectile(thrownProjectile), }, + BaseScp1509Pickup baseScp1509 => new Scp1509Pickup(baseScp1509), _ => new Pickup(pickupBase), }; } @@ -521,6 +523,7 @@ public static IEnumerable Get(IEnumerable gameObjects) TimeGrenade => new TimeGrenadeProjectile(type), _ => new Projectile(type), }, + BaseScp1509Pickup => new Scp1509Pickup(); _ => new Pickup(type), }; diff --git a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs index 32b53550b4..2bc02e728c 100644 --- a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs @@ -33,7 +33,7 @@ internal Scp1509Pickup(BaseScp1509 pickupBase) /// Initializes a new instance of the class. ///
internal Scp1509Pickup() - : base(ItemType.Radio) + : base(ItemType.SCP1509) { Base = (BaseScp1509)((Pickup)this).Base; } From 549fcf1f5eeddf21c269b333c27ad6d1354d309e Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:05:16 -0400 Subject: [PATCH 179/224] fix: EscapingEventArgs::IsAllowed forces escape (#666) Fix --- .../EventArgs/Player/EscapingEventArgs.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 9e80978aa2..2e94fd0e6d 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs @@ -7,21 +7,19 @@ namespace Exiled.Events.EventArgs.Player { - using System.Collections.Generic; - using API.Features; using Exiled.API.Enums; using Interfaces; using PlayerRoles; - using Respawning; - /// /// Contains all information before a player escapes. /// public class EscapingEventArgs : IPlayerEvent, IDeniableEvent { + private EscapeScenario escapeScenario; + /// /// Initializes a new instance of the class. /// @@ -55,7 +53,11 @@ public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeSc /// /// Gets or sets the EscapeScenario that will represent for this player. /// - public EscapeScenario EscapeScenario { get; set; } + public EscapeScenario EscapeScenario + { + get => (escapeScenario is EscapeScenario.None && IsAllowed) ? EscapeScenario.CustomEscape : escapeScenario; + set => escapeScenario = value; + } /// /// Gets or sets a value indicating whether the player can escape. From f87db1ece6f461b5e9ac4aa1894ff130a09c6fc4 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:43:10 -0400 Subject: [PATCH 180/224] fix: Fix for new SL version (#669) * why did you do this Yamato * remove test line * Fix RespawningTeam * Fix RoomType * PrefabType Update * fix ChoosingStartTeamQueue * TryRemovePostfixes changed for other Holiday * Update TranslationConversionInternal --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/EXILED.props | 2 +- EXILED/Exiled.API/Enums/PrefabType.cs | 30 +++++++++++++++ .../Extensions/DamageTypeExtensions.cs | 38 +++---------------- EXILED/Exiled.API/Features/Pickups/Pickup.cs | 2 +- .../Features/Pickups/Scp1509Pickup.cs | 29 ++++++++------ EXILED/Exiled.API/Features/Room.cs | 12 +++++- .../Handlers/Internal/ClientStarted.cs | 2 + .../Events/Server/ChoosingStartTeamQueue.cs | 4 +- .../Patches/Events/Server/RespawningTeam.cs | 12 +++--- 9 files changed, 74 insertions(+), 57 deletions(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index b8f3cb4262..793baab491 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.0-rc.2 + 9.10.0-rc.3 false diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index a67180d52e..6a52ce7995 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -387,5 +387,35 @@ public enum PrefabType [Prefab(3938583646, "TantrumObj (Brown Candy)")] TantrumObjBrownCandy, + + [Prefab(1145481038, "Scp1509Pickup")] + Scp1509Pickup, + + [Prefab(1359696107, "Hubert Moon")] + HubertMoon, + + [Prefab(4075838184, "Scp018Projectile Halloween")] + Scp018ProjectileHalloween, + + [Prefab(3262457219, "JailbirdPickup Halloween")] + JailbirdPickupHalloween, + + [Prefab(1712001893, "Scp1509PedestalStructure Variant")] + Scp1509PedestalStructureVariant, + + [Prefab(517392265, "SCP-173 Ragdoll - MrNutty Sombrero Variant")] + Scp173RagdollMrNuttySombreroVariant, + + [Prefab(2174094462, "SCP-049 Ragdoll Halloween")] + Scp049RagdollHalloween, + + [Prefab(4255002108, "SCP-096 Ragdoll Halloween")] + Scp096RagdollHalloween, + + [Prefab(2556293836, "Zombie Ragdoll Halloween")] + ZombieRagdollHalloween, + + [Prefab(213466224, "SCP-939 Ragdoll Halloween")] + Scp939RagdollHalloween, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 3420f2053b..0eea49b520 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -7,51 +7,22 @@ namespace Exiled.API.Extensions { + using System.Collections.Generic; + using System.Linq; + using Enums; using Features; using InventorySystem.Items.Scp1509; using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.PlayableScps.Scp3114; using PlayerStatsSystem; - using System.Collections.Generic; /// /// A set of extensions for . /// public static class DamageTypeExtensions { - private static readonly Dictionary TranslationIdConversionInternal = new() - { - { DeathTranslations.Asphyxiated.Id, DamageType.Asphyxiation }, - { DeathTranslations.Bleeding.Id, DamageType.Bleeding }, - { DeathTranslations.Crushed.Id, DamageType.Crushed }, - { DeathTranslations.Decontamination.Id, DamageType.Decontamination }, - { DeathTranslations.Explosion.Id, DamageType.Explosion }, - { DeathTranslations.Falldown.Id, DamageType.Falldown }, - { DeathTranslations.Poisoned.Id, DamageType.Poison }, - { DeathTranslations.Recontained.Id, DamageType.Recontainment }, - { DeathTranslations.Scp049.Id, DamageType.Scp049 }, - { DeathTranslations.Scp096.Id, DamageType.Scp096 }, - { DeathTranslations.Scp173.Id, DamageType.Scp173 }, - { DeathTranslations.Scp207.Id, DamageType.Scp207 }, - { DeathTranslations.Scp939Lunge.Id, DamageType.Scp939 }, - { DeathTranslations.Scp939Other.Id, DamageType.Scp939 }, - { DeathTranslations.Scp3114Slap.Id, DamageType.Scp3114 }, - { DeathTranslations.Tesla.Id, DamageType.Tesla }, - { DeathTranslations.Unknown.Id, DamageType.Unknown }, - { DeathTranslations.Warhead.Id, DamageType.Warhead }, - { DeathTranslations.Zombie.Id, DamageType.Scp0492 }, - { DeathTranslations.BulletWounds.Id, DamageType.Firearm }, - { DeathTranslations.PocketDecay.Id, DamageType.PocketDimension }, - { DeathTranslations.SeveredHands.Id, DamageType.SeveredHands }, - { DeathTranslations.FriendlyFireDetector.Id, DamageType.FriendlyFireDetector }, - { DeathTranslations.UsedAs106Bait.Id, DamageType.FemurBreaker }, - { DeathTranslations.MicroHID.Id, DamageType.MicroHid }, - { DeathTranslations.Hypothermia.Id, DamageType.Hypothermia }, - { DeathTranslations.MarshmallowMan.Id, DamageType.Marshmallow }, - { DeathTranslations.Scp1344.Id, DamageType.SeveredEyes }, - { DeathTranslations.Scp1509.Id, DamageType.Scp1509 }, - }; + private static readonly Dictionary TranslationIdConversionInternal = TranslationConversionInternal.ToDictionary(x => x.Key.Id, x => x.Value); private static readonly Dictionary TranslationConversionInternal = new() { @@ -83,6 +54,7 @@ public static class DamageTypeExtensions { DeathTranslations.Hypothermia, DamageType.Hypothermia }, { DeathTranslations.MarshmallowMan, DamageType.Marshmallow }, { DeathTranslations.Scp1344, DamageType.SeveredEyes }, + { DeathTranslations.Scp1509, DamageType.Scp1509 }, }; private static readonly Dictionary ItemConversionInternal = new() diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index 9894b43277..5dcceb6135 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -523,7 +523,7 @@ public static IEnumerable Get(IEnumerable gameObjects) TimeGrenade => new TimeGrenadeProjectile(type), _ => new Projectile(type), }, - BaseScp1509Pickup => new Scp1509Pickup(); + BaseScp1509Pickup => new Scp1509Pickup(), _ => new Pickup(type), }; diff --git a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs index 2bc02e728c..ff9efbac21 100644 --- a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs @@ -7,10 +7,10 @@ namespace Exiled.API.Features.Pickups { - using Exiled.API.Enums; using Exiled.API.Features.Items; - using Exiled.API.Features.Items.Keycards; using Exiled.API.Interfaces; + using InventorySystem.Items; + using InventorySystem.Items.Scp1509; using BaseScp1509 = InventorySystem.Items.Scp1509.Scp1509Pickup; @@ -63,14 +63,22 @@ internal Scp1509Pickup() /// public float UnequipDecayDelay { get; set; } + /// + /// Returns the RadioPickup in a human readable format. + /// + /// A string containing RadioPickup related data. + public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{ShieldRegenRate}| -{ShieldDecayRate}- /{ShieldOnDamagePause}/ ^{UnequipDecayDelay}^"; /// internal override void ReadItemInfo(Item item) { base.ReadItemInfo(item); - if (item is Scp1509 scp1509item) + if (item is Scp1509 scp1509Item) { - Permissions = scp1509item.Permissions; + ShieldRegenRate = scp1509Item.ShieldRegenRate; + ShieldDecayRate = scp1509Item.ShieldDecayRate; + ShieldOnDamagePause = scp1509Item.ShieldOnDamagePause; + UnequipDecayDelay = scp1509Item.UnequipDecayDelay; } } @@ -78,16 +86,13 @@ internal override void ReadItemInfo(Item item) protected override void InitializeProperties(ItemBase itemBase) { base.InitializeProperties(itemBase); - if (itemBase is Scp1509Pickup scp1509Pickup) + if (itemBase is Scp1509Item scp1509Item) { - + ShieldRegenRate = scp1509Item.ShieldRegenRate; + ShieldDecayRate = scp1509Item.ShieldDecayRate; + ShieldOnDamagePause = scp1509Item.ShieldOnDamagePause; + UnequipDecayDelay = scp1509Item.UnequipDecayDelay; } } - - /// - /// Returns the RadioPickup in a human readable format. - /// - /// A string containing RadioPickup related data. - public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{BatteryLevel}| -{Range}- /{IsEnabled}/"; } } diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index 3e971fecb2..68d696dc65 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -17,6 +17,7 @@ namespace Exiled.API.Features using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; using MapGeneration; + using MapGeneration.Holidays; using MapGeneration.Rooms; using MEC; using Mirror; @@ -436,7 +437,7 @@ internal void InternalCreate() Type = FindType(gameObject); if (Type is RoomType.Unknown) - Log.Warn($"[ROOMTYPE UNKNOWN] {Identifier} Name : {gameObject?.name} Shape : {Identifier?.Shape}"); + Log.Warn($"[ROOMTYPE UNKNOWN] {Identifier} Name : {gameObject?.name.RemoveBracketsOnEndOfName()} Shape : {Identifier?.Shape}"); RoomLightControllers = RoomLightControllersValue.AsReadOnly(); @@ -460,7 +461,7 @@ internal void InternalCreate() private static RoomType FindType(GameObject gameObject) { // Try to remove brackets if they exist. - return gameObject.name.RemoveBracketsOnEndOfName() switch + return TryRemovePostfixes(gameObject.name.RemoveBracketsOnEndOfName()) switch { "PocketWorld" => RoomType.Pocket, "Outside" => RoomType.Surface, @@ -536,5 +537,12 @@ private static RoomType FindType(GameObject gameObject) _ => RoomType.Unknown, }; } + + private static string TryRemovePostfixes(string str) + { + if (HolidayUtils.IsAnyHolidayActive()) + str.Replace(HolidayUtils.GetActiveHoliday().ToString(), string.Empty).TrimEnd(); + return str; + } } } diff --git a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs index 7fd6df8af0..9e9f6eacd7 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/ClientStarted.cs @@ -78,6 +78,8 @@ public static void OnClientStarted() prefabs.Remove(value.Value.Key); continue; } + + Log.Warn($"Useless prefab {prefabType}: {attribute.Name} ({attribute.AssetId})"); } foreach (KeyValuePair missing in prefabs) diff --git a/EXILED/Exiled.Events/Patches/Events/Server/ChoosingStartTeamQueue.cs b/EXILED/Exiled.Events/Patches/Events/Server/ChoosingStartTeamQueue.cs index f1474cd1d5..d5d80d9250 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/ChoosingStartTeamQueue.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/ChoosingStartTeamQueue.cs @@ -32,8 +32,8 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Dup) + offset; + int offset = 1; + int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(YamlConfig), nameof(YamlConfig.GetString)))) + offset; newInstructions.InsertRange( index, diff --git a/EXILED/Exiled.Events/Patches/Events/Server/RespawningTeam.cs b/EXILED/Exiled.Events/Patches/Events/Server/RespawningTeam.cs index 47ae586a94..501f6d90e6 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/RespawningTeam.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/RespawningTeam.cs @@ -50,11 +50,11 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - private static List GetPlayers(List hubs) => hubs.Select(Player.Get).ToList(); + private static List GetPlayers(ReferenceHub[] hubs) => hubs.Select(Player.Get).ToList(); - private static List GetHubs(List players) => players.Select(player => player.ReferenceHub).ToList(); + private static ReferenceHub[] GetHubs(List players) => players.Select(player => player.ReferenceHub).ToArray(); } } From e7087398682084f1e7e7577f596cebf2e6a308ef Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:51:05 +0200 Subject: [PATCH 181/224] doc: fix Scp1509Pickup::ToString() --- EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs index ff9efbac21..b11c1cb0e9 100644 --- a/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Scp1509Pickup.cs @@ -64,9 +64,9 @@ internal Scp1509Pickup() public float UnequipDecayDelay { get; set; } /// - /// Returns the RadioPickup in a human readable format. + /// Returns the Scp1509Pickup in a human readable format. /// - /// A string containing RadioPickup related data. + /// A string containing Scp1509Pickup related data. public override string ToString() => $"{Type} ({Serial}) [{Weight}] *{Scale}* |{ShieldRegenRate}| -{ShieldDecayRate}- /{ShieldOnDamagePause}/ ^{UnequipDecayDelay}^"; /// From 4601d0a527d6592c85567cf186ba444e2ba91a70 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:21:00 +0200 Subject: [PATCH 182/224] Yamato is blind and atSomeone too :3 --- EXILED/Exiled.API/Features/Room.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index 68d696dc65..a81a24173f 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -541,7 +541,7 @@ private static RoomType FindType(GameObject gameObject) private static string TryRemovePostfixes(string str) { if (HolidayUtils.IsAnyHolidayActive()) - str.Replace(HolidayUtils.GetActiveHoliday().ToString(), string.Empty).TrimEnd(); + return str.Replace(HolidayUtils.GetActiveHoliday().ToString(), string.Empty).TrimEnd(); return str; } } From 7f5588a8e8bdcd465f16d05a00a935e1adcd70bf Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:47:08 +0200 Subject: [PATCH 183/224] Fix: server crash --- EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 0eea49b520..95dfc33727 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -22,8 +22,6 @@ namespace Exiled.API.Extensions /// public static class DamageTypeExtensions { - private static readonly Dictionary TranslationIdConversionInternal = TranslationConversionInternal.ToDictionary(x => x.Key.Id, x => x.Value); - private static readonly Dictionary TranslationConversionInternal = new() { { DeathTranslations.Asphyxiated, DamageType.Asphyxiation }, @@ -57,6 +55,8 @@ public static class DamageTypeExtensions { DeathTranslations.Scp1509, DamageType.Scp1509 }, }; + private static readonly Dictionary TranslationIdConversionInternal = TranslationConversionInternal.ToDictionary(x => x.Key.Id, x => x.Value); + private static readonly Dictionary ItemConversionInternal = new() { { ItemType.GunCrossvec, DamageType.Crossvec }, From 4e08447b2bd9b3fefa437d661b7aee838ab3d549 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:35:56 +0200 Subject: [PATCH 184/224] Scp1509Resurrected missing in EffectTypeExtension --- EXILED/Exiled.API/Extensions/EffectTypeExtension.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index aca90b95f3..c70a6194b8 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -99,6 +99,7 @@ public static class EffectTypeExtension { EffectType.TemporaryBypass, typeof(TemporaryBypass) }, { EffectType.TraumatizedByEvil, typeof(TraumatizedByEvil) }, { EffectType.WhiteCandy, typeof(WhiteCandy) }, + { EffectType.Scp1509Resurrected, typeof(Scp1509Resurrected) }, #pragma warning restore CS0618 }); From 632f2074a527b48015f1f12881537464f2f17b2d Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:55:29 +0200 Subject: [PATCH 185/224] v9.10.0 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 793baab491..6641061025 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.0-rc.3 + 9.10.0 false From 7103e7185abf4b11cff427735c659f69856b809b Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Fri, 24 Oct 2025 22:03:24 +0300 Subject: [PATCH 186/224] fix: PreAuth patch fix (#672) fix: index update Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.Events/Patches/Events/Player/PreAuthenticating.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Events/Patches/Events/Player/PreAuthenticating.cs b/EXILED/Exiled.Events/Patches/Events/Player/PreAuthenticating.cs index 9c81f8f274..160b2992bc 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/PreAuthenticating.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/PreAuthenticating.cs @@ -56,7 +56,7 @@ private static IEnumerable Transpiler(IEnumerable Date: Sun, 26 Oct 2025 01:37:17 +0300 Subject: [PATCH 187/224] feat: SCP-1509 events + new properties (#670) * feat: new events * ref: revert unnecessary comment change * Fix: Scp1509:AttackingEvent * replace Scp1509\InspectingEventArgs to Item.InspectingItem * Remove AttackingEvent * oups - re-add InspectingItemEventArgs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Items/Scp1509.cs | 86 ++++++++++++++ .../Scp1509/ResurrectingEventArgs.cs | 68 +++++++++++ .../Scp1509/TriggeringAttackEventArgs.cs | 46 ++++++++ EXILED/Exiled.Events/Handlers/Scp1509.cs | 41 +++++++ .../Scp1509/InspectingAndTriggeringAttack.cs | 108 ++++++++++++++++++ .../Patches/Events/Scp1509/Resurrecting.cs | 87 ++++++++++++++ 6 files changed, 436 insertions(+) create mode 100644 EXILED/Exiled.Events/EventArgs/Scp1509/ResurrectingEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp1509/TriggeringAttackEventArgs.cs create mode 100644 EXILED/Exiled.Events/Handlers/Scp1509.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp1509/InspectingAndTriggeringAttack.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp1509/Resurrecting.cs diff --git a/EXILED/Exiled.API/Features/Items/Scp1509.cs b/EXILED/Exiled.API/Features/Items/Scp1509.cs index 87c7837fb7..b6c51626e2 100644 --- a/EXILED/Exiled.API/Features/Items/Scp1509.cs +++ b/EXILED/Exiled.API/Features/Items/Scp1509.cs @@ -7,8 +7,13 @@ namespace Exiled.API.Features.Items { + using System.Collections.Generic; + using System.Linq; + + using Exiled.API.Enums; using Exiled.API.Interfaces; using InventorySystem.Items.Scp1509; + using PlayerRoles; /// /// A wrapper class for . @@ -38,6 +43,11 @@ internal Scp1509() /// public new Scp1509Item Base { get; } + /// + /// Gets the instance. + /// + public Scp1509RespawnEligibility RespawnEligibility => Base._respawnEligibility; + /// /// Gets or sets the shield regeneration rate. /// @@ -74,6 +84,82 @@ public float UnequipDecayDelay set => Base.UnequipDecayDelay = value; } + /// + /// Gets or sets the time when resurrection ability will be available again. + /// + public double NextResurrectTime + { + get => Base._nextResurrectTime; + set => Base._nextResurrectTime = value; + } + + /// + /// Gets or sets the cooldown for a melee attack. + /// + public float MeleeCooldown + { + get => Base._meleeCooldown; + set => Base._meleeCooldown = value; // TODO not syned with clients, tests required + } + + /// + /// Gets or sets the amount of AHP bonus that all revived players are receiving. + /// + public float RevivedAhpBonus + { + get => Base._revivedPlayerAOEBonusAHP; + set => Base._revivedPlayerAOEBonusAHP = value; + } + + /// + /// Gets or sets the distance in which all revived players will receive AHP bonus. + /// + public float RevivedAhpBonusDistance + { + get => Base._revivedPlayerAOEBonusAHPDistance; + set => Base._revivedPlayerAOEBonusAHPDistance = value; + } + + /// + /// Gets or sets the max amount of HumeShield that can owner receive. + /// + public float MaxHs + { + get => Base._equippedHS; + set => Base._equippedHS = value; + } + + /// + /// Gets or sets the duration for a revived player. + /// + public float RevivedBlurTime + { + get => Base._revivedPlayerBlurTime; + set => Base._revivedPlayerBlurTime = value; + } + + /// + /// Gets or sets all revived players. + /// + public IEnumerable RevivedPlayers + { + get => Base._revivedPlayers.Select(Player.Get); + set => Base._revivedPlayers = value.Select(x => x.ReferenceHub).ToList(); + } + + /// + /// Gets a player that is eligible for respawn as a . + /// + /// Role to respawn. + /// Found player or null. + public Player GetEligibleSpectator(RoleTypeId roleTypeId) => Player.Get(RespawnEligibility.GetEligibleSpectator(roleTypeId)); + + /// + /// Checks if there is any eligible spectator for spawn. + /// + /// true if any spectator is found. Otherwise, false. + public bool IsAnyEligibleSpectators() => RespawnEligibility.IsAnyEligibleSpectators(); + /// /// Clones current object. /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp1509/ResurrectingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1509/ResurrectingEventArgs.cs new file mode 100644 index 0000000000..4484f3b730 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp1509/ResurrectingEventArgs.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp1509 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Scp1509; + using PlayerRoles; + + /// + /// Contains all information before player is resurrected. + /// + public class ResurrectingEventArgs : IItemEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + public ResurrectingEventArgs(Player target, Player victim, RoleTypeId newRole, Scp1509Item scp1509, bool isAllowed = true) + { + Target = target; + Victim = victim; + NewRole = newRole; + Scp1509 = Item.Get(scp1509); + Player = Scp1509.Owner; + IsAllowed = isAllowed; + } + + /// + /// Gets or sets the role which will be set to the after resurrection. + /// + public RoleTypeId NewRole { get; set; } + + /// + /// Gets the target of resurrection. + /// + public Player Target { get; } + + /// + public Player Player { get; } + + /// + /// Gets the victim of this kill. + /// + public Player Victim { get; } + + /// + /// Gets the SCP-1509 instance. + /// + public Scp1509 Scp1509 { get; } + + /// + public Item Item => Scp1509; + + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp1509/TriggeringAttackEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1509/TriggeringAttackEventArgs.cs new file mode 100644 index 0000000000..cbc9b54106 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp1509/TriggeringAttackEventArgs.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp1509 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Scp1509; + + /// + /// Contains all information before SCP-1509 melee attack is triggered. + /// + public class TriggeringAttackEventArgs : IItemEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public TriggeringAttackEventArgs(Scp1509Item scp1509Item, bool isAllowed = true) + { + Scp1509 = Item.Get(scp1509Item); + Player = Scp1509.Owner; + IsAllowed = isAllowed; + } + + /// + public Player Player { get; } + + /// + public Item Item => Scp1509; + + /// + /// Gets the SCP-1509 instance. + /// + public Scp1509 Scp1509 { get; } + + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Scp1509.cs b/EXILED/Exiled.Events/Handlers/Scp1509.cs new file mode 100644 index 0000000000..9a8d09d8da --- /dev/null +++ b/EXILED/Exiled.Events/Handlers/Scp1509.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Handlers +{ + using Exiled.Events.EventArgs.Scp1509; + using Exiled.Events.Features; +#pragma warning disable SA1623 + + /// + /// SCP-1509 related events. + /// + public static class Scp1509 + { + /// + /// Invoked before player is resurrected. + /// + public static Event Resurrecting { get; set; } = new(); + + /// + /// Invoked before SCP-1509 melee attack is triggered. + /// + public static Event TriggeringAttack { get; set; } = new(); + + /// + /// Called before SCP-1509 melee attack is triggered. + /// + /// The instance. + public static void OnTriggeringAttack(TriggeringAttackEventArgs ev) => TriggeringAttack.InvokeSafely(ev); + + /// + /// Called before player is resurrected. + /// + /// The instance. + public static void OnResurrecting(ResurrectingEventArgs ev) => Resurrecting.InvokeSafely(ev); + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Scp1509/InspectingAndTriggeringAttack.cs b/EXILED/Exiled.Events/Patches/Events/Scp1509/InspectingAndTriggeringAttack.cs new file mode 100644 index 0000000000..3b53a07e5d --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp1509/InspectingAndTriggeringAttack.cs @@ -0,0 +1,108 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp1509 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Item; + using Exiled.Events.EventArgs.Scp1509; + using HarmonyLib; + using InventorySystem.Items.Scp1509; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add , and events. + /// + [EventPatch(typeof(Handlers.Scp1509), nameof(Handlers.Item.InspectingItem))] + [EventPatch(typeof(Handlers.Scp1509), nameof(Handlers.Item.InspectedItem))] + [EventPatch(typeof(Handlers.Scp1509), nameof(Handlers.Scp1509.TriggeringAttack))] + [HarmonyPatch(typeof(Scp1509Item), nameof(Scp1509Item.ServerProcessCmd))] + internal class InspectingAndTriggeringAttack + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = -2; + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Stfld) + offset; + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange(index, new[] + { + // this + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + + // true + new(OpCodes.Ldc_I4_1), + + // TriggeringAttackEventArgs ev = new(this, true); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(TriggeringAttackEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Scp1509.OnTriggeringAttack(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp1509), nameof(Handlers.Scp1509.OnTriggeringAttack))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(TriggeringAttackEventArgs), nameof(TriggeringAttackEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + }); + + index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0); + + newInstructions.InsertRange(index, new[] + { + // this + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + + // true + new(OpCodes.Ldc_I4_1), + + // InspectingItemEventArgs ev = new(this, 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, retLabel), + }); + + offset = 1; + index = newInstructions.FindLastIndex(x => x.Calls(Method(typeof(Scp1509Item), nameof(Scp1509Item.SendRpc)))) + offset; + + newInstructions.InsertRange(index, new[] + { + // this + new CodeInstruction(OpCodes.Ldarg_0), + + // new InspectedItemEventArgs(this); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(InspectedItemEventArgs))[0]), + + // Handlers.Scp1509.OnInspecting(ev); + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnInspectedItem))), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + + 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/Scp1509/Resurrecting.cs b/EXILED/Exiled.Events/Patches/Events/Scp1509/Resurrecting.cs new file mode 100644 index 0000000000..cf9c490079 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp1509/Resurrecting.cs @@ -0,0 +1,87 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp1509 +{ + 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.Scp1509; + using HarmonyLib; + using InventorySystem.Items.Scp1509; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add event. + /// + [EventPatch(typeof(Handlers.Scp1509), nameof(Handlers.Scp1509.Resurrecting))] + [HarmonyPatch(typeof(Scp1509Item), nameof(Scp1509Item.ServerApplyResurrectEffects))] + internal class Resurrecting + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldarg_2); + + LocalBuilder ev = generator.DeclareLocal(typeof(ResurrectingEventArgs)); + + Label continueLabel = generator.DefineLabel(); + + newInstructions.InsertRange(index, new[] + { + // Player.Get(resurrectedPlayer) + new(OpCodes.Ldarg_2), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // Player.Get(victim) + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // respawnRole + new(OpCodes.Ldarg_3), + + // this + new(OpCodes.Ldarg_0), + + // true + new(OpCodes.Ldc_I4_1), + + // ResurrectingEventArgs ev = new(Player.Get(resurrectedPlayer), Player.Get(victim), respawnRole, this, true); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ResurrectingEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp1509.OnResurrecting(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp1509), nameof(Handlers.Scp1509.OnResurrecting))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(ResurrectingEventArgs), nameof(ResurrectingEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, continueLabel), + + new(OpCodes.Ret), + + // respawnRole = ev.NewRole; + new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex).WithLabels(continueLabel), + new(OpCodes.Callvirt, PropertyGetter(typeof(ResurrectingEventArgs), nameof(ResurrectingEventArgs.NewRole))), + new(OpCodes.Starg_S, 3), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file From e6390e175233b64159e9d6f291dc0f15c927df2b Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:34:13 +0100 Subject: [PATCH 188/224] fix: put Debug for a NW bug on RespawningTeam --- .../EventArgs/Server/RespawningTeamEventArgs.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs index aecdf33a55..df999d90dd 100644 --- a/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs @@ -14,7 +14,9 @@ namespace Exiled.Events.EventArgs.Server using Exiled.API.Features.Waves; using Exiled.Events.EventArgs.Interfaces; using PlayerRoles; + using PlayerRoles.Spectating; using Respawning; + using Respawning.Objectives; using Respawning.Waves; /// @@ -39,11 +41,22 @@ public class RespawningTeamEventArgs : IDeniableEvent public RespawningTeamEventArgs(List players, int maxRespawn, SpawnableWaveBase wave) { Players = players; + if (Players.Remove(null)) + { + string debug = string.Empty; + foreach (ReferenceHub hub in ReferenceHub.AllHubs) + { + if (WaveSpawner.CanBeSpawned(hub) && hub.roleManager.CurrentRole is SpectatorRole spectatorRole) + debug += $"({Player.Get(hub)}) {hub.GetNickname()} (ActiveTime: {spectatorRole.ActiveTime}) [{hub.authManager.InstanceMode}]\n"; + } + + Log.Error("(RespawningTeamEventArgs) preventing a null player to spawn:\n" + debug); + } + MaximumRespawnAmount = maxRespawn; SpawnQueue = new(); Wave = new TimedWave((TimeBasedWave)wave); Wave.PopulateQueue(SpawnQueue, MaximumRespawnAmount); - IsAllowed = true; } /// @@ -82,7 +95,7 @@ public int MaximumRespawnAmount /// /// Gets or sets a value indicating whether the spawn can occur. /// - public bool IsAllowed { get; set; } + public bool IsAllowed { get; set; } = true; /// /// Gets or sets the RoleTypeId spawn queue. From 1db85cd8706b507de03c9cceb60351b3ec9d9cdf Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 1 Nov 2025 22:55:34 +0100 Subject: [PATCH 189/224] Fix Lift API --- EXILED/Exiled.API/Features/Lift.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index de7dcfabb7..469a33bd22 100644 --- a/EXILED/Exiled.API/Features/Lift.cs +++ b/EXILED/Exiled.API/Features/Lift.cs @@ -19,6 +19,7 @@ namespace Exiled.API.Features using Interactables.Interobjects; using Interactables.Interobjects.DoorUtils; using UnityEngine; + using Utils; using static Interactables.Interobjects.ElevatorChamber; @@ -80,7 +81,7 @@ internal Lift(ElevatorChamber elevator) /// /// Gets a of in the . /// - public IEnumerable Players => Player.List.Where(x => Bounds.Contains(x.Position)); + public IEnumerable Players => Player.List.Where(x => RelativeBounds.Contains(x.Position)); /// /// Gets the lift's name. @@ -127,7 +128,13 @@ public ElevatorSequence Status /// /// Gets the representing the space inside the lift. /// - public Bounds Bounds => Base.WorldspaceBounds; + [Obsolete("It's now necessary to use RelativeBounds instead", true)] + public Bounds Bounds => Base.WorldspaceBounds.Bounds; + + /// + /// Gets the representing the space inside the lift. + /// + public RelativeBounds RelativeBounds => Base.WorldspaceBounds; /// /// Gets the lift's . @@ -250,7 +257,7 @@ public float AnimationTime /// /// The . /// A or if not found. - public static Lift Get(Vector3 position) => Get(lift => lift.Bounds.Contains(position)).FirstOrDefault(); + public static Lift Get(Vector3 position) => Get(lift => lift.RelativeBounds.Contains(position)).FirstOrDefault(); /// /// Gets a of filtered based on a predicate. @@ -310,7 +317,7 @@ public void ChangeLock(DoorLockReason lockReason) /// /// The position. /// if the point is inside the elevator. Otherwise, . - public bool IsInElevator(Vector3 point) => Bounds.Contains(point); + public bool IsInElevator(Vector3 point) => RelativeBounds.Contains(point); /// /// Returns the Lift in a human-readable format. From a768664fd370505da7ef190b6d1a9ac8da388e14 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 1 Nov 2025 22:59:23 +0100 Subject: [PATCH 190/224] fix: Added null check into MirrorExtensions (#677) --- .../Exiled.API/Extensions/MirrorExtensions.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 90982b9220..605e6466c0 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -560,6 +560,9 @@ public static void MessageTranslated(this Player player, string words, string tr /// The position to change. public static void MoveNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 pos) { + if (identity == null) + return; + identity.gameObject.transform.position = pos; ObjectDestroyMessage objectDestroyMessage = new() { @@ -607,6 +610,9 @@ public static void ChangeSceneToAllClients(ScenesType scene) /// The scale the object needs to be set to. public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 scale) { + if (identity == null) + return; + identity.gameObject.transform.localScale = scale; ObjectDestroyMessage objectDestroyMessage = new() { @@ -624,6 +630,9 @@ public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentit /// The position to change. public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vector3 pos) { + if (identity == null) + return; + identity.gameObject.transform.position = pos; ObjectDestroyMessage objectDestroyMessage = new() { @@ -644,6 +653,9 @@ public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vect /// The scale the object needs to be set to. public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vector3 scale) { + if (identity == null) + return; + identity.gameObject.transform.localScale = scale; ObjectDestroyMessage objectDestroyMessage = new() { @@ -668,7 +680,7 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec /// Value of send to target. public static void SendFakeSyncVar(this Player target, NetworkIdentity behaviorOwner, Type targetType, string propertyName, T value) { - if (!target.IsConnected) + if (!target.IsConnected || behaviorOwner == null) return; NetworkWriterPooled writer = NetworkWriterPool.Get(); @@ -712,7 +724,12 @@ void CustomSyncVarGenerator(NetworkWriter targetWriter) /// of object that owns . /// 's type. /// Property name starting with Network. - public static void ResyncSyncVar(NetworkIdentity behaviorOwner, Type targetType, string propertyName) => SetDirtyBitsMethodInfo.Invoke(behaviorOwner.gameObject.GetComponent(targetType), new object[] { SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"] }); + public static void ResyncSyncVar(NetworkIdentity behaviorOwner, Type targetType, string propertyName) + { + if (behaviorOwner == null) + return; + SetDirtyBitsMethodInfo.Invoke(behaviorOwner.gameObject.GetComponent(targetType), new object[] { SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"] }); + } /// /// Send fake values to client's . @@ -724,7 +741,7 @@ void CustomSyncVarGenerator(NetworkWriter targetWriter) /// Values of send to target. public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwner, Type targetType, string rpcName, params object[] values) { - if (!target.IsConnected) + if (!target.IsConnected || behaviorOwner == null) return; NetworkWriterPooled writer = NetworkWriterPool.Get(); From 204b1cd9bd53b33b45bb6c7f69046ae0e09e2635 Mon Sep 17 00:00:00 2001 From: PUDGE133 <47304666+PUDGE133@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:33:33 +0300 Subject: [PATCH 191/224] fix: group(rank) and renaming of some properties (#676) * Bug fixes and renaming of some properties to more understandable ones. We need to test this to make sure it doesn't cause problems, because God only knows how it all works internally. https://discord.com/channels/656673194693885975/1002713309876854924/1432873468285948055 * Revert "Bug fixes and renaming of some properties to more understandable ones. We need to test this to make sure it doesn't cause problems, because God only knows how it all works internally." This reverts commit c72eabf3101abc825deef716ac2858f3315e75b1. * Fix a fix * Update EXILED/Exiled.API/Features/Player.cs * Fix RemoteAdminPermissions property --------- Co-authored-by: PUDGE Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Player.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index cba7d0864d..e9e0f5a3e6 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -564,7 +564,11 @@ public Quaternion Rotation public PlayerPermissions RemoteAdminPermissions { get => (PlayerPermissions)ReferenceHub.serverRoles.Permissions; - set => ReferenceHub.serverRoles.Permissions = (ulong)value; + set + { + ReferenceHub.serverRoles.Permissions = (ulong)value; + ReferenceHub.serverRoles.FinalizeSetGroup(); + } } /// @@ -1915,7 +1919,7 @@ public bool TryGetItem(ushort serial, out Item item) } /// - /// Sets the player's rank. + /// Receives an existing rank(group) or, if it doesn't exist, creates a new one and assigns it to this player. /// /// The rank name to be set. /// The group to be set. @@ -1923,17 +1927,11 @@ public void SetRank(string name, UserGroup group) { if (ServerStatic.PermissionsHandler.Groups.TryGetValue(name, out UserGroup userGroup)) { - userGroup.BadgeColor = group.BadgeColor; - userGroup.BadgeText = name; - userGroup.HiddenByDefault = !group.Cover; - userGroup.Cover = group.Cover; - ReferenceHub.serverRoles.SetGroup(userGroup, false, false); } else { ServerStatic.PermissionsHandler.Groups.Add(name, group); - ReferenceHub.serverRoles.SetGroup(group, false, false); } From ed2f69cc73c9ae8c03933e2e97cd05ce159d8277 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sun, 2 Nov 2025 11:35:04 -0500 Subject: [PATCH 192/224] fix: Make GetEffectType not brick server when no key is found (#678) * Thing * Update EXILED/Exiled.API/Extensions/EffectTypeExtension.cs --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Extensions/EffectTypeExtension.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index c70a6194b8..c44a32362d 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Extensions using CustomPlayerEffects; using CustomRendering; using Enums; + using Exiled.API.Features; using InventorySystem.Items.MarshmallowMan; using InventorySystem.Items.Usables.Scp244.Hypothermia; using PlayerRoles.FirstPersonControl; @@ -131,7 +132,15 @@ public static bool TryGetType(this EffectType effect, out Type type) /// The enum. /// The . public static EffectType GetEffectType(this StatusEffectBase statusEffectBase) - => TypeToEffectType.TryGetValue(statusEffectBase.GetType(), out EffectType effect) ? effect : throw new InvalidOperationException("Invalid effect status base provided"); + { + if (!TypeToEffectType.TryGetValue(statusEffectBase.GetType(), out EffectType type)) + { + Log.Warn($"Missing EffectType for Type {statusEffectBase.GetType()}!!! This issue likely originates from a new update or a CustomEffect on your Server"); + return EffectType.None; + } + + return type; + } /// /// Gets the of the specified . From dd9d0afa502256dad46b3b4a2a748e91744beb4f Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:36:54 +0100 Subject: [PATCH 193/224] v9.10.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 6641061025..cb0cd48926 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.0 + 9.10.1 false From 378158e1cc8db5f410c25aec4c1cc1ea39c810a2 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:00:53 -0500 Subject: [PATCH 194/224] feat: update project settings (#667) * thing * update events csproj * set lang to 13.0 * prevent IDE0305 message --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/.editorconfig | 1 + EXILED/EXILED.props | 2 +- EXILED/Exiled.API/Exiled.API.csproj | 2 ++ EXILED/Exiled.Events/Exiled.Events.csproj | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/EXILED/.editorconfig b/EXILED/.editorconfig index 3124f1d41f..b64d58c275 100644 --- a/EXILED/.editorconfig +++ b/EXILED/.editorconfig @@ -19,6 +19,7 @@ ij_wrap_on_typing = false csharp_style_var_for_built_in_types = false:error csharp_style_var_when_type_is_apparent = false:error csharp_style_var_elsewhere = false:error +dotnet_diagnostic.IDE0305.severity = none # ReSharper properties resharper_csharp_max_line_length = 400 diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index cb0cd48926..f67ef6f8da 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -7,7 +7,7 @@ net48 - 9.0 + 13.0 x64 false $(MSBuildThisFileDirectory)\bin\$(Configuration)\ diff --git a/EXILED/Exiled.API/Exiled.API.csproj b/EXILED/Exiled.API/Exiled.API.csproj index 84dd05ff53..31fe36e002 100644 --- a/EXILED/Exiled.API/Exiled.API.csproj +++ b/EXILED/Exiled.API/Exiled.API.csproj @@ -40,6 +40,8 @@ + + diff --git a/EXILED/Exiled.Events/Exiled.Events.csproj b/EXILED/Exiled.Events/Exiled.Events.csproj index 620625057d..c8d9aa1cd1 100644 --- a/EXILED/Exiled.Events/Exiled.Events.csproj +++ b/EXILED/Exiled.Events/Exiled.Events.csproj @@ -37,6 +37,8 @@ + + From e4a57e0584b395e235d6c99d479bb45e8c048ab5 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:11:51 -0500 Subject: [PATCH 195/224] feat: Load & Enable LabAPI plugins in Plugins folder (#662) * Pain and suffering * Add plugin priority * Allow deep plugin inheritance I think idk tbh this is all really confusing * Fix accidental double enumeration idk how, but the plugin tried to load a plugin twice for me, this at least eliminates the weirdness of seeing the load message twice * Fix Reload / Disable, add GetLabAPIPlugin --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.Loader/ConfigManager.cs | 188 ++++++++++++++++++++++++++ EXILED/Exiled.Loader/Loader.cs | 137 +++++++++++++++++++ 2 files changed, 325 insertions(+) diff --git a/EXILED/Exiled.Loader/ConfigManager.cs b/EXILED/Exiled.Loader/ConfigManager.cs index e925517be9..f04835e61c 100644 --- a/EXILED/Exiled.Loader/ConfigManager.cs +++ b/EXILED/Exiled.Loader/ConfigManager.cs @@ -11,6 +11,7 @@ namespace Exiled.Loader using System.Collections.Generic; using System.IO; using System.Linq; + using System.Reflection; using API.Enums; using API.Extensions; @@ -19,13 +20,19 @@ namespace Exiled.Loader using Exiled.API.Features; using Exiled.API.Features.Pools; + using LabApi.Loader.Features.Plugins.Configuration; using YamlDotNet.Core; + using YamlDotNet.Serialization; + + using LabPlugin = LabApi.Loader.Features.Plugins.Plugin; /// /// Used to handle plugin configs. /// public static class ConfigManager { + private static readonly MethodInfo PropertiesSetter = typeof(LabPlugin).GetProperty("Properties", BindingFlags.Public | BindingFlags.Instance)?.GetSetMethod(true); + /// /// Loads all the plugin configs. /// @@ -281,5 +288,186 @@ public static void ReloadRemoteAdmin() player.ReferenceHub.serverRoles.RefreshPermissions(); } } + + /// + /// Reloads all LabAPI configs. + /// + public static void ReloadLabAPIConfigs() + { + try + { + // this is 10x more readable than Exileds current config management system LOL + foreach (LabPlugin plugin in Loader.LabAPIPlugins.Keys) + { + LoadLabAPIConfig(plugin); + SaveLabAPIConfig(plugin); + } + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + /// + /// Attempts to load a config for a LabAPI plugin. + /// + /// The LabAPI plugin. + /// I love it when the modding framework people call the best has all plugin loading methods private and the config loading methods don't take custom directory paths. + public static void LoadLabAPIConfig(LabPlugin plugin) + { + Type pluginType = plugin.GetType(); + Type type = pluginType; + while (type is not null) + { + type = type.BaseType; + + if (type is { IsGenericType: true }) + { + Type genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(LabApi.Loader.Features.Plugins.Plugin<>)) + break; + } + } + + if (type is null) + return; + + Type configType = type.GetGenericArguments().FirstOrDefault(); + if (configType is null) + { + Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, could not get the generic of TConfig!"); + return; + } + + ConstructorInfo parameterless = configType.GetConstructors().FirstOrDefault(ctor => ctor.GetParameters().Length == 0); + + if (parameterless is null) + { + Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, config type has no parameterless constructor!"); + return; + } + + MethodInfo configSetter = pluginType.GetProperty("Config")?.GetSetMethod(); + if (configSetter is null) + { + Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, no setter for property \"Config\" was found!"); + return; + } + + string configPath = Paths.GetConfigPath(plugin.Name); + + if (!File.Exists(configPath)) + { + Log.Warn($"LabAPI Plugin {plugin.Name} doesn't have default configs, generating..."); + configSetter.Invoke(plugin, new[] { parameterless.Invoke(null) }); + return; + } + + IDeserializer deserializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Deserializer; + MethodInfo deserialize = deserializer.GetType().GetMethods().Single(method => method.Name == "Deserialize" && method.IsGenericMethod && method.GetParameters().FirstOrDefault()?.ParameterType == typeof(string)).MakeGenericMethod(configType); + try + { + configSetter.Invoke(plugin, new[] { deserialize.Invoke(deserializer, new object[] { File.ReadAllText(configPath) }) }); + } + catch (TargetInvocationException ex) + { + if (ex.InnerException is YamlException yamlException) + { + Log.Error($"{plugin.Name} configs could not be loaded, some of them are in a wrong format, default configs will be loaded instead!\n{yamlException}"); + configSetter.Invoke(plugin, new[] { parameterless.Invoke(null) }); + } + else + { + throw; + } + } + } + + /// + /// Saves a config for a LabAPI plugin. + /// + /// The LabAPI plugin. + public static void SaveLabAPIConfig(LabPlugin plugin) + { + Type pluginType = plugin.GetType(); + Type type = pluginType; + while (type is not null) + { + type = type.BaseType; + + if (type is { IsGenericType: true }) + { + Type genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(LabApi.Loader.Features.Plugins.Plugin<>)) + break; + } + } + + if (type is null) + return; + + MethodInfo configGetter = pluginType.GetProperty("Config")?.GetGetMethod(); + if (configGetter is null) + { + Log.Error($"Failed to save config for LabAPI plugin {plugin.Name}, no getter for property \"Config\" was found!"); + return; + } + + string config = LabApi.Loader.Features.Yaml.YamlConfigParser.Serializer.Serialize(configGetter.Invoke(plugin, null)); + string configPath = Paths.GetConfigPath(plugin.Name); + + try + { + Directory.CreateDirectory(Path.Combine(Paths.IndividualConfigs, plugin.Name)); + File.WriteAllText(configPath, config); + } + catch (Exception exception) + { + Log.Error($"An error has occurred while saving configs to {configPath} path: {exception}"); + } + } + + /// + /// Loads the properties of a LabAPI plugin. + /// + /// The LabAPI plugin. + /// Whether the properties were successfully retrieved. + public static bool LoadLabAPIProperties(LabPlugin plugin) + { + if (PropertiesSetter is null) + { + Log.Error("Cannot load LabAPI properties as the setter from reflection is null!"); + return false; + } + + ISerializer serializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Serializer; + IDeserializer deserializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Deserializer; + + string configPath = Path.Combine(Paths.IndividualConfigs, plugin.Name, $"{Server.Port}-properties.yml"); + + if (!File.Exists(configPath)) + { + Log.Warn($"LabAPI Plugin {plugin.Name} doesn't have default properties, generating..."); + PropertiesSetter.Invoke(plugin, new object[] { Properties.CreateDefault() }); + File.WriteAllText(configPath, serializer.Serialize(plugin.Properties!)); + return true; + } + + try + { + PropertiesSetter.Invoke(plugin, new[] { deserializer.Deserialize(File.ReadAllText(configPath), typeof(Properties)) }); + } + catch (YamlException yamlException) + { + Log.Error($"{plugin.Name} properties could not be loaded, default properties will be loaded instead!\n{yamlException}"); + PropertiesSetter.Invoke(plugin, new object[] { Properties.CreateDefault() }); + File.WriteAllText(configPath, serializer.Serialize(plugin.Properties!)); + } + + return true; + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs index 18ef26fdcd..5dce161c0d 100644 --- a/EXILED/Exiled.Loader/Loader.cs +++ b/EXILED/Exiled.Loader/Loader.cs @@ -23,17 +23,25 @@ namespace Exiled.Loader using CommandSystem.Commands.Shared; using Exiled.API.Features; + using Exiled.API.Features.Pools; using Features; using Features.Configs; using Features.Configs.CustomConverters; + using LabApi.Loader; + using LabApi.Loader.Features.Misc; + using LabApi.Loader.Features.Plugins.Configuration; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NodeDeserializers; + using LabPlugin = LabApi.Loader.Features.Plugins.Plugin; + /// /// Used to handle plugins. /// public class Loader { + private static readonly MethodInfo FilePathSetter = typeof(LabPlugin).GetProperty("FilePath", BindingFlags.Public | BindingFlags.Instance)?.GetSetMethod(true); + /// /// Initializes a new instance of the class. /// @@ -67,6 +75,11 @@ public Loader() /// public static SortedSet> Plugins { get; } = new(PluginPriorityComparer.Instance); + /// + /// Gets the plugins list. + /// + public static Dictionary LabAPIPlugins { get; } = new(); + /// /// Gets a dictionary containing the file paths of assemblies. /// @@ -226,6 +239,60 @@ public static IPlugin CreatePlugin(Assembly assembly) return null; } + /// + /// Create a plugin instance. + /// + /// The plugin assembly. + /// The path of the assembly. + /// Returns the created plugin instance or . + public static LabPlugin CreateLabAPIPlugin(Assembly assembly, string path) + { + try + { + AssemblyUtils.ResolveEmbeddedResources(assembly); + } + catch (Exception ex) + { + Log.Error("Failed to resolve embedded resources for assembly '" + path + "'"); + string[] missingDependencies = AssemblyUtils.GetMissingDependencies(assembly).ToArray(); + if (!missingDependencies.Any()) + return null; + Log.Error("Missing dependencies:\n" + string.Join("\n", missingDependencies.Select(x => "-\t " + x))); + Log.Error(ex); + } + + LabPlugin plugin = null; + + if (FilePathSetter is null) + { + Log.Error("FilePath setter for LabAPI Plugin type is null!"); + } + + try + { + foreach (Type type in assembly.GetTypes()) + { + if (!type.IsSubclassOf(typeof(LabPlugin)) || type.IsAbstract || Activator.CreateInstance(type) is not LabPlugin instance) + continue; + + FilePathSetter?.Invoke(instance, new object[] { path }); + + plugin = instance; + } + } + catch (Exception ex) + { + Log.Error(" Couldn't load the LabAPI plugin inside '" + path + "'"); + string[] missingDependencies = AssemblyUtils.GetMissingDependencies(assembly).ToArray(); + if (!missingDependencies.Any()) + return null; + Log.Error("Missing dependencies:\n" + string.Join("\n", missingDependencies.Select(x => "-\t " + x))); + Log.Error(ex); + } + + return plugin; + } + /// /// Enables all plugins. /// @@ -268,6 +335,38 @@ public static void EnablePlugins() Log.Error($"Plugin \"{plugin.Name}\" threw an exception while enabling: {exception}"); } } + + foreach (LabPlugin plugin in LabAPIPlugins.Keys.OrderBy(plugin => plugin.Priority)) + { + try + { + if (ConfigManager.LoadLabAPIProperties(plugin)) + { + Properties properties = plugin.Properties; + if (properties is { IsEnabled: true }) + { + // copy pasted from LabAPI plugin enabling + try + { + CustomNetworkManager.Modded = true; + plugin.RegisterCommands(); + plugin.Enable(); + + Log.Info($"LabAPI plugin {plugin.Name} v{plugin.Version.Major}.{plugin.Version.Minor}.{plugin.Version.Build} by {plugin.Author} has been enabled!"); + } + catch (Exception ex) + { + Log.Error($"Couldn't enable the LabAPI plugin {plugin}"); + Log.Error(ex); + } + } + } + } + catch (Exception exception) + { + Log.Error($"Plugin \"{plugin.Name}\" threw an exception while enabling: {exception}"); + } + } } /// @@ -294,12 +393,14 @@ public static void ReloadPlugins() } Plugins.Clear(); + LabAPIPlugins.Clear(); Server.PluginAssemblies.Clear(); Locations.Clear(); LoadPlugins(); ConfigManager.Reload(); + ConfigManager.ReloadLabAPIConfigs(); TranslationManager.Reload(); EnablePlugins(); @@ -322,6 +423,12 @@ public static void DisablePlugins() Log.Error($"Plugin \"{plugin.Name}\" threw an exception while disabling: {exception}"); } } + + foreach (LabPlugin plugin in LabAPIPlugins.Keys) + { + plugin.UnregisterCommands(); + plugin.Disable(); + } } /// @@ -331,6 +438,14 @@ public static void DisablePlugins() /// The desired plugin, null if not found. public static IPlugin GetPlugin(string args) => Plugins.FirstOrDefault(x => x.Name == args || x.Prefix == args); + /// + /// Gets a LabAPI plugin Exiled loaded by its name. + /// + /// The name of the plugin. + /// The desired plugin, null if not found. + /// This method does not check LabAPI's loaded plugins, only LabAPI plugins EXILED loaded. + public static LabPlugin GetLabAPIPlugin(string args) => LabAPIPlugins.Keys.FirstOrDefault(x => x.Name == args); + /// /// Runs the plugin manager, by loading all dependencies, plugins, configs and then enables all plugins. /// @@ -391,6 +506,7 @@ public IEnumerator Run(Assembly[] dependencies = null) LoadPlugins(); ConfigManager.Reload(); + ConfigManager.ReloadLabAPIConfigs(); TranslationManager.Reload(); EnablePlugins(); @@ -485,6 +601,7 @@ private static void LoadPluginsFromDirectory(string dir = null) Locations[assembly] = assemblyPath; } + List failed = ListPool.Pool.Get(); foreach (Assembly assembly in Locations.Keys) { if (Locations[assembly].Contains("dependencies")) @@ -493,7 +610,10 @@ private static void LoadPluginsFromDirectory(string dir = null) IPlugin plugin = CreatePlugin(assembly); if (plugin == null) + { + failed.Add(assembly); continue; + } if (Plugins.Any(p => p.Name == plugin.Name)) continue; @@ -505,6 +625,23 @@ private static void LoadPluginsFromDirectory(string dir = null) Server.PluginAssemblies.Add(assembly, plugin); Plugins.Add(plugin); } + + // inefficient enumeration (could be forced inside earlier foreach), but a lot easier to read. + foreach (Assembly attempt in failed) + { + LabPlugin plugin = CreateLabAPIPlugin(attempt, Locations[attempt]); + + if (plugin == null) + continue; + + if (Plugins.Any(p => p.Name == plugin.Name) || LabAPIPlugins.Keys.Any(p => p.Name == plugin.Name)) + continue; + + Log.Info("Successfully loaded LabAPI plugin " + plugin.Name); + LabAPIPlugins.Add(plugin, attempt); + } + + ListPool.Pool.Return(failed); } /// From b5b7455a21fc5541fcbddd1b67c1dd427fb703dc Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:30:26 +0100 Subject: [PATCH 196/224] Update T4 file --- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- EXILED/docs/articles/SCPSLRessources/NW_Documentation.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 990fa0ca9f..a4e454cb3a 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, 2, 0, 0); + public static readonly Version RequiredSCPSLVersion = new(14, 2, 0, 1); } } \ No newline at end of file diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index f9be26f3be..f797797370 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.2.0.0) +Last Update (14.2.0.1) ### Index @@ -5797,7 +5797,7 @@ Last Update (14.2.0.0)
Damage Handlers -```md title="Latest Updated: 14.2.0.0" +```md title="Latest Updated: 14.2.0.1" All available DamageHandlers + Symbol ':' literally means "inherits from" From 9144e087927a38ac9eff7ac86ba2e14290d18864 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:41:08 +0100 Subject: [PATCH 197/224] fix: OnItemAdded being called after OnItemRemoved (#675) * Fix OnItemAdded being called after OnItemRemoved * Update T4 file * FixOnAddedBeingCallAfterOnRemoved --- EXILED/Exiled.API/Features/Player.cs | 4 +- .../FixOnAddedBeingCallAfterOnRemoved.cs | 66 +++++++++---------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index e9e0f5a3e6..ea317a5d5d 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2924,6 +2924,8 @@ public Item AddItem(ItemBase itemBase, Item item = null, ItemAddReason addReason Inventory.UserInventory.Items[item.Serial] = itemBase; + typeof(InventoryExtensions).InvokeStaticEvent(nameof(InventoryExtensions.OnItemAdded), new object[] { ReferenceHub, itemBase, null }); + item.ChangeOwner(item.Owner, this); if (itemBase is IAcquisitionConfirmationTrigger acquisitionConfirmationTrigger) @@ -2931,8 +2933,6 @@ public Item AddItem(ItemBase itemBase, Item item = null, ItemAddReason addReason acquisitionConfirmationTrigger.AcquisitionAlreadyReceived = false; } - typeof(InventoryExtensions).InvokeStaticEvent(nameof(InventoryExtensions.OnItemAdded), new object[] { ReferenceHub, itemBase, null }); - Inventory.SendItemsNextFrame = true; return item; } diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixOnAddedBeingCallAfterOnRemoved.cs b/EXILED/Exiled.Events/Patches/Fixes/FixOnAddedBeingCallAfterOnRemoved.cs index 7b432fefce..95ec63e4cc 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/FixOnAddedBeingCallAfterOnRemoved.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/FixOnAddedBeingCallAfterOnRemoved.cs @@ -37,16 +37,8 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); - Label continueLabel = generator.DefineLabel(); - - int offset = -1; - int index = newInstructions.FindLastIndex(instruction => instruction.Calls(PropertyGetter(typeof(NetworkBehaviour), nameof(NetworkBehaviour.isLocalPlayer)))) + offset; - - // set label for code right after OnAdded/OnItemAdded, to skip that part for ammo - Label afterAmmoLabel = newInstructions[index].labels[0]; - - offset = -2; - index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(ItemBase), nameof(ItemBase.OnAdded)))) + offset; + int offset = -2; + int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(ItemBase), nameof(ItemBase.OnAdded)))) + offset; newInstructions.InsertRange( index, @@ -56,26 +48,40 @@ private static IEnumerable Transpiler(IEnumerable onItemAdded = InventoryExtensions.OnItemAdded; + if (onItemAdded != null) + { + onItemAdded(inv._hub, itemBase2, pickup); + } + // To this + Action onItemAdded = InventoryExtensions.OnItemAdded; + if (onItemAdded != null) + { + onItemAdded(inv._hub, itemBase2, pickup); + } + itemBase2.OnAdded(pickup); + */ + int opCodesToMove = 3; + offset = -2; - // move after basegame OnAdded/OnItemAdded - new(OpCodes.Br_S, afterAmmoLabel), + int indexOnAdded = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(ItemBase), nameof(ItemBase.OnAdded)))) + offset; + offset = 1; - new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel), - }); + int indexInvoke = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(Action), nameof(Action.Invoke)))) + offset; + + // insert new OnAdded before the Event InventoryExtensions.OnItemAdded + newInstructions.InsertRange(indexInvoke, newInstructions.GetRange(indexOnAdded, opCodesToMove)); + + // move Label to not skip the OnAdded + newInstructions[indexInvoke].MoveLabelsFrom(newInstructions[indexInvoke + opCodesToMove]); + + // remove the old OnAdded + newInstructions.RemoveRange(indexOnAdded, opCodesToMove); for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; @@ -83,12 +89,6 @@ private static IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } - private static void InverseCall(ItemBase item, ReferenceHub referenceHub, ItemPickupBase pickup) - { - Exiled.API.Extensions.ReflectionExtensions.InvokeStaticEvent(typeof(InventoryExtensions), nameof(InventoryExtensions.OnItemAdded), new object[] { referenceHub, item, pickup }); - item.OnAdded(pickup); - } - private static void CallBefore(ItemBase itemBase, ItemPickupBase pickupBase) { Item item = Item.Get(itemBase); From 22fcfefec9640296e98d3a6a64a0b2942c86ed15 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:22:05 -0500 Subject: [PATCH 198/224] fix: Loader accidentally loading duplicate LabAPI plugins already loaded by LabAPI Loader --- EXILED/Exiled.Loader/Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs index 5dce161c0d..b8a8883fa3 100644 --- a/EXILED/Exiled.Loader/Loader.cs +++ b/EXILED/Exiled.Loader/Loader.cs @@ -634,7 +634,7 @@ private static void LoadPluginsFromDirectory(string dir = null) if (plugin == null) continue; - if (Plugins.Any(p => p.Name == plugin.Name) || LabAPIPlugins.Keys.Any(p => p.Name == plugin.Name)) + if (PluginLoader.EnabledPlugins.Any(p => p.Name == plugin.Name) || Plugins.Any(p => p.Name == plugin.Name) || LabAPIPlugins.Keys.Any(p => p.Name == plugin.Name)) continue; Log.Info("Successfully loaded LabAPI plugin " + plugin.Name); From 1521dec1dcafeb2993f608aba30979476b2efb7f Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:11:01 +0100 Subject: [PATCH 199/224] fix: Updater ExiledDirectoryPath not being respected This was found by the Github GPT and i am surprised of it --- EXILED/Exiled.Loader/Updater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 54eaf927ec..672c17063a 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -229,7 +229,7 @@ private void Update(HttpClient client, NewVersion newVersion) FileName = installerPath, UseShellExecute = false, CreateNoWindow = true, - Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{Path.Combine(Paths.Exiled, "..")}\" {(newVersion.IsPrerelease ? "--pre-releases" : string.Empty)}", + Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{config.ExiledDirectoryPath}\" {(newVersion.IsPrerelease ? "--pre-releases" : string.Empty)}", RedirectStandardOutput = true, RedirectStandardError = true, StandardErrorEncoding = ProcessEncoding, From 3a44a4591a7d9bd40dbf548c872d88dd0742894f Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:19:29 -0500 Subject: [PATCH 200/224] version bump --- EXILED/EXILED.props | 2 +- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index f67ef6f8da..e2cd7117d5 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.1 + 9.10.2 false diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index a4e454cb3a..5758b5d895 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, 2, 0, 1); + public static readonly Version RequiredSCPSLVersion = new(14, 2, 0, 2); } } \ No newline at end of file From 7df90e88f6b60ee51121dcd5818848ad2ddd0a69 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:44:33 -0500 Subject: [PATCH 201/224] feat: PR for 14.2.3 (#689) * thing * Fix Error * more thing * Fix Jailbird HitReg being removed * 14.2.3 - Documentation & Version update * Player::Cassie(global::Cassie.CassieAnnouncement) * Lift::ElevatorAutoReturn and doc * HELPPP HELP MEEEEEE HELPPPPPPPPPP AAAAAAAAAAAAAAAAAAAAA (this stuff is untested btw) * Fixes * consolidate stuff in some event args --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Enums/CameraType.cs | 2 + .../Exiled.API/Extensions/MirrorExtensions.cs | 33 ++-- EXILED/Exiled.API/Features/Camera.cs | 2 + EXILED/Exiled.API/Features/Cassie.cs | 101 ++++++++---- EXILED/Exiled.API/Features/Items/Jailbird.cs | 24 +-- EXILED/Exiled.API/Features/Lift.cs | 7 + .../Features/Pickups/JailbirdPickup.cs | 10 +- EXILED/Exiled.API/Features/Player.cs | 15 ++ EXILED/Exiled.API/Features/Recontainer.cs | 16 +- EXILED/Exiled.API/Features/Waves/TimedWave.cs | 4 +- .../Cassie/SendingCassieMessageEventArgs.cs | 144 +++++++++++++++--- .../Map/AnnouncingScpTerminationEventArgs.cs | 18 +-- .../Events/Cassie/SendingCassieMessage.cs | 54 ++----- .../Events/Map/AnnouncingChaosEntrance.cs | 11 +- .../Events/Map/AnnouncingNtfEntrance.cs | 25 +-- .../Events/Map/AnnouncingNtfMiniEntrance.cs | 12 +- .../Events/Map/AnnouncingScpTermination.cs | 108 ++++++++----- .../Events/Map/AnnouncingTeamEntrance.cs | 68 --------- .../Patches/Events/Scp079/Recontaining.cs | 7 +- .../Patches/Fixes/Jailbird914CoarseFix.cs | 2 +- .../Patches/Fixes/JailbirdHitRegFix.cs | 61 -------- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- .../SCPSLRessources/NW_Documentation.md | 76 ++++++--- 23 files changed, 439 insertions(+), 363 deletions(-) delete mode 100644 EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs delete mode 100644 EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs diff --git a/EXILED/Exiled.API/Enums/CameraType.cs b/EXILED/Exiled.API/Enums/CameraType.cs index 535e7366ce..1a193d16db 100644 --- a/EXILED/Exiled.API/Enums/CameraType.cs +++ b/EXILED/Exiled.API/Enums/CameraType.cs @@ -159,6 +159,8 @@ public enum CameraType EzGateAElevators, EzGateBInterior, EzGateBSide, + EzGateAStairwell, + EzGateAUpper, #endregion } } diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 605e6466c0..3ba47fd9a8 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -17,6 +17,7 @@ namespace Exiled.API.Extensions using AdminToys; using AudioPooling; + using Cassie; using CustomPlayerEffects; using Exiled.API.Enums; using Exiled.API.Features.Items; @@ -512,13 +513,12 @@ public static void ResyncKeycardPickup(CustomKeycardPickup customKeycard) /// Same on 's isSubtitles. public static void PlayCassieAnnouncement(this Player player, string words, bool makeHold = false, bool makeNoise = true, bool isSubtitles = false) { - foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers) - { - if (controller != null) - { - SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), words, makeHold, makeNoise, isSubtitles); - } - } + CassieAnnouncement announcement = new(new CassieTtsPayload(words, isSubtitles, makeHold), 0, makeNoise ? 1 : 0); + + // processes makeNoise + announcement.OnStartedPlaying(); + + announcement.Payload.SendToHubsConditionally(hub => hub == player.ReferenceHub); } /// @@ -533,23 +533,12 @@ public static void PlayCassieAnnouncement(this Player player, string words, bool /// Same on 's isSubtitles. 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(); + CassieAnnouncement announcement = new(new CassieTtsPayload(words, customSubtitles, makeHold), 0, makeNoise ? 1 : 0); - string[] cassies = words.Split('\n'); - string[] translations = translation.Split('\n'); + // processes makeNoise + announcement.OnStartedPlaying(); - for (int i = 0; i < cassies.Length; i++) - announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} "); - - string message = StringBuilderPool.Pool.ToStringReturn(announcement); - - foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers) - { - if (controller != null) - { - SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), message, makeHold, makeNoise, isSubtitles, customSubtitles); - } - } + announcement.Payload.SendToHubsConditionally(hub => hub == player.ReferenceHub); } /// diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index fca5cbbc73..4601a5ada1 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -143,6 +143,8 @@ public class Camera : IWrapper, IWorldSpace ["GATE A ELEVATORS"] = CameraType.EzGateAElevators, ["GATE B INTERIOR"] = CameraType.EzGateBInterior, ["GATE B SIDE"] = CameraType.EzGateBSide, + ["GATE A STAIRWELL"] = CameraType.EzGateAStairwell, + ["GATE A UPPER"] = CameraType.EzGateAUpper, // CustomCamera ["EZ ARM CAMERA TOY"] = CameraType.EzArmCameraToy, diff --git a/EXILED/Exiled.API/Features/Cassie.cs b/EXILED/Exiled.API/Features/Cassie.cs index 0730f9a5a9..42f0911ddd 100644 --- a/EXILED/Exiled.API/Features/Cassie.cs +++ b/EXILED/Exiled.API/Features/Cassie.cs @@ -7,19 +7,19 @@ namespace Exiled.API.Features { + using System; using System.Collections.Generic; using System.Linq; using System.Text; using Exiled.API.Features.Pools; - + using global::Cassie; + using global::Cassie.Interpreters; using MEC; - using PlayerRoles; - using PlayerStatsSystem; - using Respawning; + using Respawning.NamingRules; using CustomFirearmHandler = DamageHandlers.FirearmDamageHandler; using CustomHandlerBase = DamageHandlers.DamageHandlerBase; @@ -29,20 +29,15 @@ namespace Exiled.API.Features /// public static class Cassie { - /// - /// Gets the singleton. - /// - public static NineTailedFoxAnnouncer Announcer => NineTailedFoxAnnouncer.singleton; - /// /// Gets a value indicating whether C.A.S.S.I.E is currently announcing. Does not include decontamination or Alpha Warhead Messages. /// - public static bool IsSpeaking => Announcer.queue.Count != 0; + public static bool IsSpeaking => CassieAnnouncementDispatcher.AllAnnouncements.Count != 0; /// - /// Gets a of objects that C.A.S.S.I.E recognizes. + /// Gets a of objects that C.A.S.S.I.E recognizes. /// - public static IReadOnlyCollection VoiceLines => Announcer.voiceLines; + public static IReadOnlyCollection VoiceLines => CassieAnnouncementDispatcher.AllAnnouncements; /// /// Reproduce a non-glitched C.A.S.S.I.E message. @@ -52,7 +47,7 @@ public static class Cassie /// Indicates whether C.A.S.S.I.E has to make noises during the message. /// Indicates whether C.A.S.S.I.E has to make subtitles. public static void Message(string message, bool isHeld = false, bool isNoisy = true, bool isSubtitles = false) => - RespawnEffectsController.PlayCassieAnnouncement(message, isHeld, isNoisy, isSubtitles); + new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue(); /// /// Reproduce a non-glitched C.A.S.S.I.E message with a possibility to custom the subtitles. @@ -70,7 +65,7 @@ public static void MessageTranslated(string message, string translation, bool is for (int i = 0; i < cassies.Length; i++) announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} "); - RespawnEffectsController.PlayCassieAnnouncement(announcement.ToString(), isHeld, isNoisy, isSubtitles); + new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue(); StringBuilderPool.Pool.Return(announcement); } @@ -81,7 +76,7 @@ public static void MessageTranslated(string message, string translation, bool is /// The chance of placing a glitch between each word. /// The chance of jamming each word. public static void GlitchyMessage(string message, float glitchChance, float jamChance) => - Announcer.ServerOnlyAddGlitchyPhrase(message, glitchChance, jamChance); + new CassieAnnouncement(new CassieTtsPayload(CassieGlitchifier.Glitchify(message, glitchChance, jamChance), true, true), 0f, 0f).AddToQueue(); /// /// Reproduce a non-glitched C.A.S.S.I.E message after a certain amount of seconds. @@ -92,7 +87,7 @@ public static void GlitchyMessage(string message, float glitchChance, float jamC /// Indicates whether C.A.S.S.I.E has to make noises during the message. /// Indicates whether C.A.S.S.I.E has to make subtitles. public static void DelayedMessage(string message, float delay, bool isHeld = false, bool isNoisy = true, bool isSubtitles = false) => - Timing.CallDelayed(delay, () => RespawnEffectsController.PlayCassieAnnouncement(message, isHeld, isNoisy, isSubtitles)); + Timing.CallDelayed(delay, () => new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue()); /// /// Reproduce a glitchy C.A.S.S.I.E announcement after a certain period of seconds. @@ -102,17 +97,45 @@ public static void DelayedMessage(string message, float delay, bool isHeld = fal /// The chance of placing a glitch between each word. /// The chance of jamming each word. public static void DelayedGlitchyMessage(string message, float delay, float glitchChance, float jamChance) => - Timing.CallDelayed(delay, () => Announcer.ServerOnlyAddGlitchyPhrase(message, glitchChance, jamChance)); + Timing.CallDelayed(delay, () => new CassieAnnouncement(new CassieTtsPayload(CassieGlitchifier.Glitchify(message, glitchChance, jamChance), true, true), 0f, 0f).AddToQueue()); /// /// Calculates the duration of a C.A.S.S.I.E message. /// /// The message, which duration will be calculated. - /// Determines if a number won't be converted to its full pronunciation. - /// The speed of the message. + /// An obsolete parameter. + /// Another obsolete parameter. /// Duration (in seconds) of specified message. - public static float CalculateDuration(string message, bool rawNumber = false, float speed = 1f) - => Announcer.CalculateDuration(message, rawNumber, speed); + public static float CalculateDuration(string message, bool obsolete1, float obsolete2) + { + if (!CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase)) + { + return 0; + } + + float value = 0; + string[] lines = message.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + CassiePlaybackModifiers modifiers = new(); + StringBuilder builder = StringBuilderPool.Pool.Get(); + + for (int i = 0; i < lines.Length; i++) + { + foreach (CassieInterpreter interpreter in CassieTtsAnnouncer.Interpreters) + { + bool halt; + foreach (CassieInterpreter.Result result in interpreter.GetResults(cassieLineDatabase, ref modifiers, lines[i], builder, out halt)) + { + value += (float)result.Modifiers.GetTimeUntilNextWord(result.Line); + } + + if (halt) + break; + } + } + + return value; + } /// /// Converts a into a Cassie-Readable CONTAINMENTUNIT. @@ -120,8 +143,16 @@ public static float CalculateDuration(string message, bool rawNumber = false, fl /// . /// Unit Name. /// Containment Unit text. - public static string ConvertTeam(Team team, string unitName) - => NineTailedFoxAnnouncer.ConvertTeam(team, unitName); + public static string ConvertTeam(Team team, string unitName) => team switch + { + Team.FoundationForces when NamingRulesManager.TryGetNamingRule(team, out UnitNamingRule unitNamingRule) => "CONTAINMENTUNIT " + unitNamingRule.TranslateToCassie(unitName), + Team.FoundationForces => "CONTAINMENTUNIT UNKNOWN", + Team.ChaosInsurgency => "BY CHAOSINSURGENCY", + Team.Scientists => "BY SCIENCE PERSONNEL", + Team.ClassD => "BY CLASSD PERSONNEL", + Team.Flamingos => "BY FLAMINGOS", + _ => "UNKNOWN", + }; /// /// Converts a number into a Cassie-Readable String. @@ -129,7 +160,23 @@ public static string ConvertTeam(Team team, string unitName) /// Number to convert. /// A CASSIE-readable representing the number. public static string ConvertNumber(int num) - => NineTailedFoxAnnouncer.ConvertNumber(num); + { + if (!CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase)) + { + return string.Empty; + } + + NumberInterpreter numberInterpreter = (NumberInterpreter)CassieTtsAnnouncer.Interpreters.FirstOrDefault((CassieInterpreter x) => x is NumberInterpreter); + if (numberInterpreter == null) + { + return string.Empty; + } + + CassiePlaybackModifiers cassiePlaybackModifiers = default; + StringBuilder stringBuilder = new(); + numberInterpreter.GetResults(cassieLineDatabase, ref cassiePlaybackModifiers, num.ToString(), stringBuilder, out bool flag); + return stringBuilder.ToString(); + } /// /// Announce a SCP Termination. @@ -137,7 +184,7 @@ public static string ConvertNumber(int num) /// SCP to announce termination of. /// HitInformation. public static void ScpTermination(Player scp, DamageHandlerBase info) - => NineTailedFoxAnnouncer.AnnounceScpTermination(scp.ReferenceHub, info); + => CassieScpTerminationAnnouncement.AnnounceScpTermination(scp.ReferenceHub, info); /// /// Announces the termination of a custom SCP name. @@ -167,14 +214,14 @@ public static void CustomScpTermination(string scpName, CustomHandlerBase info) /// /// Clears the C.A.S.S.I.E queue. /// - public static void Clear() => RespawnEffectsController.ClearQueue(); + public static void Clear() => CassieAnnouncementDispatcher.ClearAll(); /// /// Gets a value indicating whether the given word is a valid C.A.S.S.I.E word. /// /// The word to check. /// if the word is valid; otherwise, . - public static bool IsValid(string word) => Announcer.voiceLines.Any(line => line.apiName.ToUpper() == word.ToUpper()); + public static bool IsValid(string word) => CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase) ? cassieLineDatabase.AllLines.Any(line => line.ApiName.ToUpper() == word.ToUpper()) : false; /// /// Gets a value indicating whether the given sentence is all valid C.A.S.S.I.E word. diff --git a/EXILED/Exiled.API/Features/Items/Jailbird.cs b/EXILED/Exiled.API/Features/Items/Jailbird.cs index b68a3473c2..7e67d0cc73 100644 --- a/EXILED/Exiled.API/Features/Items/Jailbird.cs +++ b/EXILED/Exiled.API/Features/Items/Jailbird.cs @@ -52,8 +52,8 @@ internal Jailbird() /// public float MeleeDamage { - get => Base._hitreg._damageMelee; - set => Base._hitreg._damageMelee = value; + get => Base.MeleeDamage; + set => Base.MeleeDamage = value; } /// @@ -61,8 +61,8 @@ public float MeleeDamage /// public float ChargeDamage { - get => Base._hitreg._damageCharge; - set => Base._hitreg._damageCharge = value; + get => Base._chargeDamage; + set => Base._chargeDamage = value; } /// @@ -70,8 +70,8 @@ public float ChargeDamage /// public float FlashDuration { - get => Base._hitreg._flashedDuration; - set => Base._hitreg._flashedDuration = value; + get => Base._flashedDuration; + set => Base._flashedDuration = value; } /// @@ -79,8 +79,8 @@ public float FlashDuration /// public float ConcussionDuration { - get => Base._hitreg._concussionDuration; - set => Base._hitreg._concussionDuration = value; + get => Base._concussionDuration; + set => Base._concussionDuration = value; } /// @@ -88,8 +88,8 @@ public float ConcussionDuration /// public float Radius { - get => Base._hitreg._hitregRadius; - set => Base._hitreg._hitregRadius = value; + get => Radius; + set => Radius = value; } /// @@ -97,10 +97,10 @@ public float Radius /// public float TotalDamageDealt { - get => Base._hitreg.TotalMeleeDamageDealt; + get => Base.TotalMeleeDamageDealt; set { - Base._hitreg.TotalMeleeDamageDealt = value; + Base.TotalMeleeDamageDealt = value; Base._deterioration.RecheckUsage(); } } diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index 469a33bd22..d191cd6764 100644 --- a/EXILED/Exiled.API/Features/Lift.cs +++ b/EXILED/Exiled.API/Features/Lift.cs @@ -47,6 +47,7 @@ public class Lift : IWrapper, IWorldSpace internal Lift(ElevatorChamber elevator) { Base = elevator; + ElevatorAutoReturn = elevator.GetComponent(); ElevatorChamberToLift.Add(elevator, this); internalDoorsList.AddRange(Elevator.AllElevatorDoors[Group]); @@ -73,6 +74,12 @@ internal Lift(ElevatorChamber elevator) /// public ElevatorChamber Base { get; } + /// + /// Gets the base . + /// + /// Would be null for any elevator that do not used . + public ElevatorAutoReturn ElevatorAutoReturn { get; } + /// /// Gets a value of the internal doors list. /// diff --git a/EXILED/Exiled.API/Features/Pickups/JailbirdPickup.cs b/EXILED/Exiled.API/Features/Pickups/JailbirdPickup.cs index 0494d2e966..eaaf77cb7a 100644 --- a/EXILED/Exiled.API/Features/Pickups/JailbirdPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/JailbirdPickup.cs @@ -123,11 +123,11 @@ protected override void InitializeProperties(ItemBase itemBase) base.InitializeProperties(itemBase); if (itemBase is JailbirdItem jailbirdItem) { - MeleeDamage = jailbirdItem._hitreg._damageMelee; - ChargeDamage = jailbirdItem._hitreg._damageCharge; - FlashDuration = jailbirdItem._hitreg._flashedDuration; - ConcussionDuration = jailbirdItem._hitreg._concussionDuration; - Radius = jailbirdItem._hitreg._hitregRadius; + MeleeDamage = jailbirdItem.MeleeDamage; + ChargeDamage = jailbirdItem._chargeDamage; + FlashDuration = jailbirdItem._flashedDuration; + ConcussionDuration = jailbirdItem._concussionDuration; + Radius = Radius; } } } diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index ea317a5d5d..e635bfd650 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -1982,6 +1982,21 @@ public void Broadcast(Broadcast broadcast, bool shouldClearPrevious = false) Broadcast(broadcast.Duration, broadcast.Content, broadcast.Type, shouldClearPrevious); } + /// + /// Send an to the player. + /// + /// The to be broadcasted. + /// if Cassie failed to play it or it's play nothing, otherwise it's return the duration of the annoucement. + public float CassieAnnouncement(global::Cassie.CassieAnnouncement cassieAnnouncement) + { + global::Cassie.CassieAnnouncementDispatcher.CurrentAnnouncement.OnStartedPlaying(); + global::Cassie.CassieTtsPayload payload = cassieAnnouncement.Payload; + if (!global::Cassie.CassieTtsAnnouncer.TryPlay(payload, out float totalduration)) + return 0; + payload.SendToHubsConditionally(x => x == ReferenceHub); + return totalduration; + } + /// /// Drops an item from the player's inventory. /// diff --git a/EXILED/Exiled.API/Features/Recontainer.cs b/EXILED/Exiled.API/Features/Recontainer.cs index 17f59c97d5..dbc31a35bf 100644 --- a/EXILED/Exiled.API/Features/Recontainer.cs +++ b/EXILED/Exiled.API/Features/Recontainer.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features { + using System; using System.Collections.Generic; using System.Linq; @@ -33,7 +34,8 @@ public static class Recontainer /// /// Gets a value indicating whether the C.A.S.S.I.E is currently busy. /// - public static bool IsCassieBusy => Base.CassieBusy; + [Obsolete("Use Cassie.IsSpeaking instead")] + public static bool IsCassieBusy => Cassie.IsSpeaking; /// /// Gets a value about how many generator have been activated. @@ -61,11 +63,8 @@ public static bool IsContainmentZoneLocked /// /// Gets or sets the delay to wait before overcharging. /// - public static float OverchargeDelay - { - get => Base._activationDelay; - set => Base._activationDelay = value; - } + [Obsolete("Will be removed in Exiled 10, patch the Cassie079RecontainAnnouncement ctor if you need this functionality")] + public static float OverchargeDelay { get; set; } /// /// Gets or sets the lockdown duration. @@ -188,7 +187,7 @@ public static bool IsContainmentSequenceSuccessful /// /// The announcement to play. /// The glitchy multiplier. - public static void PlayAnnouncement(string announcement, float glitchyMultiplier) => Base.PlayAnnouncement(announcement, glitchyMultiplier); + public static void PlayAnnouncement(string announcement, float glitchyMultiplier) => Base.PlayAnnouncement(announcement, false, false, null); /// /// Begins the overcharge procedure. @@ -199,7 +198,6 @@ public static void BeginOvercharge(bool endOvercharge = true) Base.BeginOvercharge(); if (endOvercharge) { - Base._delayStopwatch.Stop(); Base._unlockStopwatch.Start(); } } @@ -228,7 +226,7 @@ public static void BeginOvercharge(bool endOvercharge = true) /// /// Begins the recontainment procedure. /// - public static void Recontain() => Base.Recontain(); + public static void Recontain() => Base.Recontain(false); /// /// Refreshes the activator. diff --git a/EXILED/Exiled.API/Features/Waves/TimedWave.cs b/EXILED/Exiled.API/Features/Waves/TimedWave.cs index 8856727c58..6e9a5c55bf 100644 --- a/EXILED/Exiled.API/Features/Waves/TimedWave.cs +++ b/EXILED/Exiled.API/Features/Waves/TimedWave.cs @@ -193,13 +193,13 @@ public static List GetTimedWaves() /// Plays the announcement for this wave. /// /// Wave must implement . - public void PlayAnnouncement() => Announcement?.PlayAnnouncement(new()); + public void PlayAnnouncement() => Announcement?.PlayAnnouncement([], Base as IAnnouncedWave); /// /// Plays the announcement for this wave. /// /// Wave must implement . /// The list of Player to spawn. - public void PlayAnnouncement(IEnumerable players) => Announcement?.PlayAnnouncement(players.Select(x => x.ReferenceHub).ToList()); + public void PlayAnnouncement(IEnumerable players) => Announcement?.PlayAnnouncement(players.Select(x => x.ReferenceHub).ToList(), Base as IAnnouncedWave); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs index a8b986c171..abd2f4877f 100644 --- a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs @@ -7,39 +7,70 @@ namespace Exiled.Events.EventArgs.Cassie { + using System; + using System.Text; + + using Exiled.API.Features.Pools; + using global::Cassie; using Interfaces; + using Subtitles; /// /// Contains all the information after sending a C.A.S.S.I.E. message. /// public class SendingCassieMessageEventArgs : IDeniableEvent { + private readonly CassieAnnouncement announcement; + private readonly CassieTtsPayload payload; + + private string customSubtitles; + private float glitchScale; + /// /// Initializes a new instance of the class. /// - /// - /// - /// - /// - /// - /// - /// - /// + /// The announcement to populate all properties from. + /// + /// /// - /// - /// - /// - /// - /// - /// - /// Indicates whether the event can be executed. - public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise, bool customAnnouncement, string customSubtitles, bool isAllowed = true) + public SendingCassieMessageEventArgs(CassieAnnouncement annc, bool isAllowed = true) { - Words = words; - CustomSubtitles = customSubtitles; - MakeHold = makeHold; - MakeNoise = makeNoise; - IsCustomAnnouncement = customAnnouncement; + announcement = annc; + payload = annc.Payload; + + Words = payload.Content; + switch (payload.SubtitleSource) + { + case CassieTtsPayload.SubtitleMode.None: + case CassieTtsPayload.SubtitleMode.Automatic: + CustomSubtitles = string.Empty; + break; + case CassieTtsPayload.SubtitleMode.Custom: + CustomSubtitles = payload._customSubtitle; + break; + case CassieTtsPayload.SubtitleMode.FromTranslation: + StringBuilder builder = StringBuilderPool.Pool.Get(); + SubtitleController controller = SubtitleController.Singleton; + + foreach (SubtitlePart part in payload._subtitleMessage.SubtitleParts) + { + Subtitle subtitle = controller.Subtitles[part.Subtitle]; + builder.Append(controller.GetTranslation(subtitle)); + } + + CustomSubtitles = StringBuilderPool.Pool.ToStringReturn(builder); + + break; + default: + CustomSubtitles = string.Empty; + break; + } + + MakeHold = payload.PlayBackground; + GlitchScale = annc.GlitchScale; + MakeNoise = annc.GlitchScale is not 0; + SubtitleSource = payload.SubtitleSource; + IsAllowed = isAllowed; } @@ -51,13 +82,38 @@ public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise /// /// Gets or sets the message subtitles. /// - public string CustomSubtitles { get; set; } + public string CustomSubtitles + { + get => customSubtitles; + set + { + if (customSubtitles != value) + SubtitleSource = CassieTtsPayload.SubtitleMode.Custom; + + customSubtitles = value; + } + } /// /// Gets or sets a value indicating whether the message should be held. /// public bool MakeHold { get; set; } + /// + /// Gets or sets a value controlling how glitchy this CASSIE message is. + /// + public float GlitchScale + { + get => glitchScale; + set + { + if (!MakeNoise && value is not 0) + MakeNoise = true; + + glitchScale = value; + } + } + /// /// Gets or sets a value indicating whether the message should make noise. /// @@ -69,8 +125,50 @@ public SendingCassieMessageEventArgs(string words, bool makeHold, bool makeNoise public bool IsAllowed { get; set; } /// - /// Gets or sets a value indicating whether the message can be sent. + /// Gets or sets a value indicating whether the event can be executed. /// + [Obsolete("Useless and will be removed in Exiled 10.")] public bool IsCustomAnnouncement { get; set; } + + /// + /// Gets or sets a value indicating where the subtitles for this message came from. + /// + public CassieTtsPayload.SubtitleMode SubtitleSource { get; set; } + + /// + /// Gets a consisting of all properties in this event. + /// + public CassieAnnouncement Announcement + { + get + { + CassieTtsPayload newPayload; + + // I love readonly fields :) + if (SubtitleSource is CassieTtsPayload.SubtitleMode.FromTranslation) + { + newPayload = new CassieTtsPayload(Words, MakeHold, payload._subtitleMessage.SubtitleParts); + } + else + { + if (SubtitleSource is CassieTtsPayload.SubtitleMode.Automatic) + newPayload = new CassieTtsPayload(Words, true, MakeHold); + else + newPayload = new CassieTtsPayload(Words, CustomSubtitles, MakeHold); + } + + return announcement switch + { + CassieScpTerminationAnnouncement => + + // this is disabled via patch b/c termination messages are not modifiable at the stage the SendCassieMessage patch is in. + throw new InvalidOperationException("SendCassieMessage was called for a SCP termination message!"), + + CassieWaveAnnouncement waveAnnc => new CassieWaveAnnouncement(waveAnnc.Wave, newPayload), + Cassie079RecontainAnnouncement recontainAnnc => new Cassie079RecontainAnnouncement(recontainAnnc._callback, false, newPayload), + _ => new CassieAnnouncement(newPayload, 0, GlitchScale / (API.Features.Warhead.IsDetonated ? 2F : 1F) * (MakeNoise ? 1F : 0F)), + }; + } + } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs index 4c43eb1df8..d8d38cc474 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/AnnouncingScpTerminationEventArgs.cs @@ -7,15 +7,13 @@ namespace Exiled.Events.EventArgs.Map { + using System; + using API.Features; using API.Features.DamageHandlers; using API.Features.Roles; - using Interfaces; - using CustomAttackerHandler = API.Features.DamageHandlers.AttackerDamageHandler; - using DamageHandlerBase = PlayerStatsSystem.DamageHandlerBase; - /// /// Contains all information before C.A.S.S.I.E announces an SCP termination. /// @@ -27,16 +25,14 @@ public class AnnouncingScpTerminationEventArgs : IAttackerEvent, IDeniableEvent /// /// /// - /// - /// + /// + /// /// - public AnnouncingScpTerminationEventArgs(Player scp, DamageHandlerBase damageHandlerBase) + public AnnouncingScpTerminationEventArgs(Player scp, string terminationCause) { Player = scp; Role = scp.Role; - DamageHandler = new CustomDamageHandler(scp, damageHandlerBase); - Attacker = DamageHandler.BaseIs(out CustomAttackerHandler customAttackerHandler) ? customAttackerHandler.Attacker : null; - TerminationCause = damageHandlerBase.CassieDeathAnnouncement.Announcement; + TerminationCause = terminationCause; IsAllowed = true; } @@ -58,11 +54,13 @@ public AnnouncingScpTerminationEventArgs(Player scp, DamageHandlerBase damageHan /// /// Gets the player who killed the SCP. /// + [Obsolete("Attacker can no longer be acquired for this event. This will be readded in a different event.")] public Player Attacker { get; } /// /// Gets or sets the . /// + [Obsolete("DamageHandler can no longer be acquired for this event. This will be readded in a different event.")] public CustomDamageHandler DamageHandler { get; set; } /// diff --git a/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs b/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs index 0cb378ac02..1e7b55e3a8 100644 --- a/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs +++ b/EXILED/Exiled.Events/Patches/Events/Cassie/SendingCassieMessage.cs @@ -8,87 +8,61 @@ namespace Exiled.Events.Patches.Events.Cassie { using System.Collections.Generic; + using System.Linq; using System.Reflection.Emit; using API.Features.Pools; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Cassie; - + using global::Cassie; using Handlers; - using HarmonyLib; using Respawning; using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Cassie), nameof(Cassie.SendingCassieMessage))] - [HarmonyPatch(typeof(RespawnEffectsController), nameof(RespawnEffectsController.PlayCassieAnnouncement))] + [HarmonyPatch(typeof(CassieAnnouncementDispatcher), nameof(CassieAnnouncementDispatcher.PlayNewAnnouncement))] internal static class SendingCassieMessage { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); + Label skipLabel = generator.DefineLabel(); Label returnLabel = generator.DefineLabel(); + newInstructions[0].WithLabels(skipLabel); + newInstructions.InsertRange( 0, new CodeInstruction[] { - // words new(OpCodes.Ldarg_0), + new(OpCodes.Isinst, typeof(CassieScpTerminationAnnouncement)), + new(OpCodes.Brtrue_S, skipLabel), - // makeHold - new(OpCodes.Ldarg_1), - - // makeNoise - new(OpCodes.Ldarg_2), - - // customAnnouncement - new(OpCodes.Ldarg_3), - - // customSubtitles - new(OpCodes.Ldarg_S, 4), + new(OpCodes.Ldarg_0), // isAllowed new(OpCodes.Ldc_I4_1), - // SendingCassieMessageEventArgs ev = new SendingCassieMessageEventArgs(string, bool, bool, bool, string, bool); - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(SendingCassieMessageEventArgs))[0]), - new(OpCodes.Dup), - new(OpCodes.Dup), - new(OpCodes.Dup), - new(OpCodes.Dup), + // SendingCassieMessageEventArgs ev = new SendingCassieMessageEventArgs(CassieAnnouncement, bool); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(SendingCassieMessageEventArgs)).Single(ctor => ctor.GetParameters().Length == 2)), new(OpCodes.Dup), new(OpCodes.Dup), // Cassie.OnSendingCassieMessage(ev); new(OpCodes.Call, Method(typeof(Cassie), nameof(Cassie.OnSendingCassieMessage))), - // words = ev.Words - new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.Words))), + // annc = ev.Announcement + new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.Announcement))), new(OpCodes.Starg_S, 0), - // makeHold = ev.MakeHold - new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.MakeHold))), - new(OpCodes.Starg_S, 1), - - // makeNoise = ev.MakeNoise - new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.MakeNoise))), - new(OpCodes.Starg_S, 2), - - // customAnnouncement = ev.IsCustomAnnouncement - new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.IsCustomAnnouncement))), - new(OpCodes.Starg_S, 3), - - // customSubtitles = ev.CustomSubtitles - new(OpCodes.Call, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.CustomSubtitles))), - new(OpCodes.Starg_S, 4), - // if (!IsAllowed) // return; new(OpCodes.Callvirt, PropertyGetter(typeof(SendingCassieMessageEventArgs), nameof(SendingCassieMessageEventArgs.IsAllowed))), diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs index 0d53ebeefb..3033a0327d 100644 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs +++ b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingChaosEntrance.cs @@ -7,6 +7,7 @@ namespace Exiled.Events.Patches.Events.Map { + using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; @@ -17,11 +18,12 @@ namespace Exiled.Events.Patches.Events.Map using Exiled.Events.EventArgs.Map; using HarmonyLib; using Respawning.Announcements; + using Subtitles; using static HarmonyLib.AccessTools; /// - /// Patches and + /// Patches and /// to add event. /// [EventPatch(typeof(Handlers.Map), nameof(Handlers.Map.AnnouncingChaosEntrance))] @@ -30,8 +32,8 @@ internal static class AnnouncingChaosEntrance { private static IEnumerable TargetMethods() { - yield return Method(typeof(ChaosWaveAnnouncement), nameof(ChaosWaveAnnouncement.CreateAnnouncementString)); - yield return Method(typeof(ChaosMiniwaveAnnouncement), nameof(ChaosMiniwaveAnnouncement.CreateAnnouncementString)); + yield return Method(typeof(ChaosWaveAnnouncement), nameof(ChaosWaveAnnouncement.CreateAnnouncement)); + yield return Method(typeof(ChaosMiniwaveAnnouncement), nameof(ChaosMiniwaveAnnouncement.CreateAnnouncement)); } private static IEnumerable Transpiler(IEnumerable instruction, ILGenerator generator) @@ -70,6 +72,9 @@ private static IEnumerable Transpiler(IEnumerable - /// Patch the + /// Patch the /// Adds the event. /// [EventPatch(typeof(Map), nameof(Map.AnnouncingNtfEntrance))] - [HarmonyPatch(typeof(NtfWaveAnnouncement), nameof(NtfWaveAnnouncement.CreateAnnouncementString))] + [HarmonyPatch(typeof(NtfWaveAnnouncement), nameof(NtfWaveAnnouncement.CreateAnnouncement))] internal static class AnnouncingNtfEntrance { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) @@ -39,8 +40,8 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Stloc_3) + offset; + int offset = 2; + int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(UnitNamingRule), nameof(UnitNamingRule.TranslateToCassie)))) + offset; newInstructions.InsertRange( index, @@ -50,10 +51,10 @@ private static IEnumerable Transpiler(IEnumerable]*?>"), new(OpCodes.Ldsfld, Field(typeof(string), nameof(string.Empty))), new(OpCodes.Call, Method(typeof(Regex), nameof(Regex.Replace), new System.Type[] { typeof(string), typeof(string), typeof(string) })), @@ -63,9 +64,9 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable - /// Patch the + /// Patch the /// Adds the event. /// [EventPatch(typeof(Map), nameof(Map.AnnouncingNtfEntrance))] - [HarmonyPatch(typeof(NtfMiniwaveAnnouncement), nameof(NtfMiniwaveAnnouncement.CreateAnnouncementString))] + [HarmonyPatch(typeof(NtfMiniwaveAnnouncement), nameof(NtfMiniwaveAnnouncement.CreateAnnouncement))] internal static class AnnouncingNtfMiniEntrance { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) @@ -35,15 +35,17 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Stloc_1) + 1; + newInstructions.InsertRange( - 0, + index, new CodeInstruction[] { // WaveAnnouncementBase new(OpCodes.Ldarg_0), // scpsLeft - new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc_1), // null new(OpCodes.Ldnull), @@ -66,7 +68,7 @@ private static IEnumerable Transpiler(IEnumerable /// Patches - /// . + /// . /// Adds the event. /// [EventPatch(typeof(Map), nameof(Map.AnnouncingScpTermination))] - [HarmonyPatch(typeof(NineTailedFoxAnnouncer), nameof(NineTailedFoxAnnouncer.AnnounceScpTermination))] + [HarmonyPatch(typeof(CassieScpTerminationAnnouncement), nameof(CassieScpTerminationAnnouncement.OnStartedPlaying))] internal static class AnnouncingScpTermination { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); - LocalBuilder ev = generator.DeclareLocal(typeof(AnnouncingScpTerminationEventArgs)); + LocalBuilder cause = generator.DeclareLocal(typeof(string)); + LocalBuilder enumerator = generator.DeclareLocal(typeof(IEnumerator)); + + ExceptionBlock beginTry = new(ExceptionBlockType.BeginExceptionBlock); + ExceptionBlock beginFinally = new(ExceptionBlockType.BeginFinallyBlock); + ExceptionBlock endFinally = new(ExceptionBlockType.EndExceptionBlock); Label ret = generator.DefineLabel(); + Label entryLabel = generator.DefineLabel(); + Label loopLabel = generator.DefineLabel(); + Label leaveLabel = generator.DefineLabel(); + Label endFinallyLabel = generator.DefineLabel(); + + int offset = -1; + int index = newInstructions.FindIndex(i => i.LoadsField(Field(typeof(CassieScpTerminationAnnouncement), nameof(CassieScpTerminationAnnouncement._announcementTts)))) + offset; + + newInstructions.RemoveRange(index, 2); + newInstructions.Insert(index, new CodeInstruction(OpCodes.Ldloc_S, cause)); - int offset = -4; - int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Newobj && (ConstructorInfo)i.operand == GetDeclaredConstructors(typeof(LabApi.Events.Arguments.ServerEvents.CassieQueuingScpTerminationEventArgs))[0]) + offset; + newInstructions[0].WithLabels(leaveLabel); newInstructions.InsertRange( - index, + 0, new[] - { - // Player.Get(scp) - new CodeInstruction(OpCodes.Ldarg_0), - new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), - - // hit - new(OpCodes.Ldarg_1), - - // AnnouncingScpTerminationEventArgs ev = new(Player, DamageHandlerBase) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(AnnouncingScpTerminationEventArgs))[0]), - new(OpCodes.Dup), - new(OpCodes.Dup), - new(OpCodes.Stloc_S, ev.LocalIndex), - - // Map.OnAnnouncingScpTermination(ev) - new(OpCodes.Call, Method(typeof(Map), nameof(Map.OnAnnouncingScpTermination))), - - // if (!ev.IsAllowed) - // return; - new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingScpTerminationEventArgs), nameof(AnnouncingScpTerminationEventArgs.IsAllowed))), - new(OpCodes.Brfalse_S, ret), - - // hit = ev.DamageHandler.Base - new(OpCodes.Ldloc_S, ev.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingScpTerminationEventArgs), nameof(AnnouncingScpTerminationEventArgs.DamageHandler))), - new(OpCodes.Callvirt, PropertyGetter(typeof(CustomDamageHandler), nameof(CustomDamageHandler.Base))), - new(OpCodes.Starg, 1), - - // announcement = ev.TerminationCause - new(OpCodes.Ldloc_S, ev.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingScpTerminationEventArgs), nameof(AnnouncingScpTerminationEventArgs.TerminationCause))), - new(OpCodes.Stloc_0), - }); + { + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CassieScpTerminationAnnouncement), nameof(CassieScpTerminationAnnouncement._announcementTts))), + new(OpCodes.Stloc_S, cause), + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(CassieScpTerminationAnnouncement), nameof(CassieScpTerminationAnnouncement.Victims))), + new(OpCodes.Callvirt, Method(typeof(IEnumerable), nameof(IEnumerable.GetEnumerator))), + new(OpCodes.Stloc_S, enumerator), + + // start of try + new CodeInstruction(OpCodes.Br_S, entryLabel).WithBlocks(beginTry), + + // start of loop + new CodeInstruction(OpCodes.Ldloc_S, enumerator).WithLabels(loopLabel), + new(OpCodes.Callvirt, PropertyGetter(typeof(IEnumerator), nameof(IEnumerator.Current))), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(Footprint) })), + new(OpCodes.Ldloc_S, cause), + new(OpCodes.Newobj, Constructor(typeof(AnnouncingScpTerminationEventArgs), new[] { typeof(Player), typeof(string) })), + new(OpCodes.Dup), + new(OpCodes.Call, Method(typeof(Map), nameof(Map.OnAnnouncingScpTermination))), + new(OpCodes.Callvirt, PropertyGetter(typeof(AnnouncingScpTerminationEventArgs), nameof(AnnouncingScpTerminationEventArgs.TerminationCause))), + new(OpCodes.Stloc_S, cause), + + // entry point + new CodeInstruction(OpCodes.Ldloc_S, enumerator).WithLabels(entryLabel), + new(OpCodes.Callvirt, Method(typeof(IEnumerator), nameof(IEnumerator.MoveNext))), + new(OpCodes.Brtrue_S, loopLabel), + + // end of loop + new(OpCodes.Leave, leaveLabel), + + // begin finally + new CodeInstruction(OpCodes.Ldloc_S, enumerator).WithBlocks(beginFinally), + new(OpCodes.Brfalse, endFinallyLabel), + new(OpCodes.Ldloc_S, enumerator), + new(OpCodes.Callvirt, Method(typeof(IDisposable), nameof(IDisposable.Dispose))), + + // end of finally + new CodeInstruction(OpCodes.Endfinally).WithLabels(endFinallyLabel).WithBlocks(endFinally), + }); newInstructions[newInstructions.Count - 1].labels.Add(ret); diff --git a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs b/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs deleted file mode 100644 index 7193f8d17f..0000000000 --- a/EXILED/Exiled.Events/Patches/Events/Map/AnnouncingTeamEntrance.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Events.Map -{ - using System.Collections.Generic; - using System.Reflection; - using System.Reflection.Emit; - using System.Text; - - using Exiled.API.Features.Pools; - using Exiled.Events.Attributes; - using Exiled.Events.EventArgs.Map; - using HarmonyLib; - using Respawning.Announcements; - - using static HarmonyLib.AccessTools; - - /// - /// Patches to prevent cassie from playing empty string. - /// - [EventPatch(typeof(Handlers.Map), nameof(Handlers.Map.AnnouncingNtfEntrance))] - [EventPatch(typeof(Handlers.Map), nameof(Handlers.Map.AnnouncingChaosEntrance))] - [HarmonyPatch(typeof(WaveAnnouncementBase), nameof(WaveAnnouncementBase.PlayAnnouncement))] - internal static class AnnouncingTeamEntrance - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - Label returnLabel = generator.DefineLabel(); - - // the instruction that sends subtitles is called before stringReturn is created (and thus checked) so we need to move it so that empty (or disallowed) message's subtitles are not sent. - // this removes the Ldarg_0 and the CallVirt - int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(WaveAnnouncementBase), nameof(WaveAnnouncementBase.SendSubtitles)))); - CodeInstruction sendSubtitlesInstruction = newInstructions[index]; - newInstructions.RemoveRange(index - 2, 3); - - index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldsfld); - - newInstructions.InsertRange(index, new[] - { - // if (stringReturn == "") - // return; - new(OpCodes.Ldloc_S, 4), - new(OpCodes.Ldstr, string.Empty), - new(OpCodes.Ceq), - new(OpCodes.Brtrue_S, returnLabel), - - // send subtitles before cassie message, but after our check. - new(OpCodes.Ldarg_0), - new(OpCodes.Ldarg_1), - sendSubtitlesInstruction, - }); - - 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/Scp079/Recontaining.cs b/EXILED/Exiled.Events/Patches/Events/Scp079/Recontaining.cs index d2efcd3fd3..680bbbf0dd 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp079/Recontaining.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp079/Recontaining.cs @@ -31,17 +31,18 @@ internal class Recontaining { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { - int index = 0; List newInstructions = ListPool.Pool.Get(instructions); LocalBuilder ev = generator.DeclareLocal(typeof(RecontainingEventArgs)); Label returnLabel = generator.DefineLabel(); - newInstructions.InsertRange(index, new CodeInstruction[] + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + 1; + + newInstructions.InsertRange(index, new[] { // RecontainingEventArgs ev = new(this._activatorGlass) - new(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), new(OpCodes.Ldfld, Field(typeof(Scp079Recontainer), nameof(Scp079Recontainer._activatorGlass))), new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RecontainingEventArgs))[0]), new(OpCodes.Stloc_S, ev.LocalIndex), diff --git a/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs b/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs index 1f6a8ee431..17bdf5e142 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/Jailbird914CoarseFix.cs @@ -22,7 +22,7 @@ namespace Exiled.Events.Patches.Fixes using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Bug reported to NW (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/88). /// [HarmonyPatch(typeof(JailbirdDeteriorationTracker), nameof(JailbirdDeteriorationTracker.Setup))] diff --git a/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs b/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs deleted file mode 100644 index 46151c7577..0000000000 --- a/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.Events.Patches.Fixes -{ - using System.Collections.Generic; - using System.Reflection.Emit; - - using API.Features.Pools; - - using HarmonyLib; - using InventorySystem.Items.Jailbird; - using Mirror; - using Utils.Networking; - - using static HarmonyLib.AccessTools; - - /// - /// Patches . - /// - [HarmonyPatch(typeof(JailbirdHitreg), nameof(JailbirdHitreg.ServerAttack))] - internal static class JailbirdHitRegFix - { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) - { - List newInstructions = ListPool.Pool.Get(instructions); - - int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(ReferenceHubReaderWriter), nameof(ReferenceHubReaderWriter.TryReadReferenceHub)))) - 2; - - int breakIndex = newInstructions.FindIndex(i => i.Calls(Method(typeof(JailbirdHitreg), nameof(JailbirdHitreg.DetectDestructibles)))) - 1; - Label breakLabel = generator.DefineLabel(); - newInstructions[breakIndex].WithLabels(breakLabel); - - List
+### CassieClipCategory + +
Cassie.CassieClipCategory + +``` + [0] = Word + [1] = Glitch + [2] = Other +``` + +
+ ### Category
PlayerRoles.PlayableScps.Scp079.Scp079KeyAbilityBase+Category @@ -1451,18 +1466,6 @@ Last Update (14.2.0.1)
-### DetectionStatus - -
InventorySystem.Items.Firearms.Modules.Scp127.Scp127CassieBasedVoiceTriggerBase+DetectionStatus - -``` - [0] = Idle - [1] = WaitingForTrigger - [2] = AnnouncementStartedPlaying -``` - -
- ### DiodeType
AlphaWarheadNukesitePanel+DiodeType @@ -2412,7 +2415,7 @@ Last Update (14.2.0.1) ### HitResult -
InventorySystem.Items.Scp1509.Scp1509Hitreg+HitResult +
InventorySystem.Items.Autosync.MeleeAutoSync+HitResult ``` [0] = HitPlayer @@ -3294,6 +3297,17 @@ Last Update (14.2.0.1)
+### NextAction + +
Cassie.Interpreters.CassieInterpreter+NextAction + +``` + [0] = Continue + [1] = Halt +``` + +
+ ### NextRoundAction
ServerStatic+NextRoundAction @@ -3500,6 +3514,18 @@ Last Update (14.2.0.1)
+### OverwatchSettings + +
PlayerRoles.Spectating.OverwatchSettings + +``` + [0] = PlayerInfoToggle + [1] = HitRegisterToggle + [2] = DebugLines +``` + +
+ ### PacketProperty
LiteNetLib.PacketProperty @@ -4181,7 +4207,9 @@ Last Update (14.2.0.1) [0] = TargetResync [1] = TargetKilled [2] = AttackInterrupted - [3] = OutOfRange + [3] = AttackCancelled + [4] = OutOfRange + [5] = StrangleTimer ```
@@ -5200,6 +5228,19 @@ Last Update (14.2.0.1)
+### SubtitleMode + +
Cassie.CassieTtsPayload+SubtitleMode + +``` + [0] = None + [1] = Automatic + [2] = Custom + [3] = FromTranslation +``` + +
+ ### SubtitleType
Subtitles.SubtitleType @@ -5434,6 +5475,7 @@ Last Update (14.2.0.1) [8] = Tokens [11] = All [16] = Spawn + [32] = Max ```
@@ -5797,7 +5839,7 @@ Last Update (14.2.0.1)
Damage Handlers -```md title="Latest Updated: 14.2.0.1" +```md title="Latest Updated: 14.2.0.3" All available DamageHandlers + Symbol ':' literally means "inherits from" From dd2290633b862ed1bd969795791bcae04332c525 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:47:26 -0500 Subject: [PATCH 202/224] version bump --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index e2cd7117d5..373507fc3c 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.10.2 + 9.11.0 false From 8df3afbc2960918ee0f8b2558fea03a1297e1d49 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Mon, 15 Dec 2025 15:36:46 -0500 Subject: [PATCH 203/224] Fix Yamato making jailbirds crash servers --- EXILED/Exiled.API/Features/Items/Jailbird.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Jailbird.cs b/EXILED/Exiled.API/Features/Items/Jailbird.cs index 7e67d0cc73..4cc9a59c86 100644 --- a/EXILED/Exiled.API/Features/Items/Jailbird.cs +++ b/EXILED/Exiled.API/Features/Items/Jailbird.cs @@ -84,12 +84,12 @@ public float ConcussionDuration } /// - /// Gets or sets the radius of the Jailbird's hit register. + /// Gets or sets the radius of the Jailbird's hit radius. /// public float Radius { - get => Radius; - set => Radius = value; + get => Base._hitregRadius; + set => Base._hitregRadius = value; } /// From 05af6130b2bca0b7696bc73d9478000839635e00 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:33:18 -0500 Subject: [PATCH 204/224] another freaking version bump --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 373507fc3c..b1e03a2c89 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.11.0 + 9.11.2 false From 464f4220ec2a34ff1495db2580750b3b65fdde9a Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:35:06 -0500 Subject: [PATCH 205/224] Fix updater --- EXILED/Exiled.Loader/Updater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Loader/Updater.cs b/EXILED/Exiled.Loader/Updater.cs index 672c17063a..a2a3863597 100644 --- a/EXILED/Exiled.Loader/Updater.cs +++ b/EXILED/Exiled.Loader/Updater.cs @@ -229,7 +229,7 @@ private void Update(HttpClient client, NewVersion newVersion) FileName = installerPath, UseShellExecute = false, CreateNoWindow = true, - Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{config.ExiledDirectoryPath}\" {(newVersion.IsPrerelease ? "--pre-releases" : string.Empty)}", + Arguments = $"--exit {(Folder == "global" ? string.Empty : $"--target-port {Folder}")} --target-version {newVersion.Release.TagName} --appdata \"{Paths.AppData}\" --exiled \"{Path.Combine(config.ExiledDirectoryPath, "..")}\" {(newVersion.IsPrerelease ? "--pre-releases" : string.Empty)}", RedirectStandardOutput = true, RedirectStandardError = true, StandardErrorEncoding = ProcessEncoding, From 936d4e71511ce866cd499cfc86f21a3992d7b862 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:03:58 +0300 Subject: [PATCH 206/224] fix: Player reloading (#688) Update Player.cs --- EXILED/Exiled.API/Features/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index e635bfd650..182215e1d8 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -636,7 +636,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences /// /// Gets a value indicating whether the player is reloading a weapon. /// - public bool IsReloading => CurrentItem is Firearm firearm && !firearm.IsReloading; + public bool IsReloading => CurrentItem is Firearm firearm && firearm.IsReloading; /// /// Gets a value indicating whether the player is aiming with a weapon. From ef044b941c215bd1a09c254e9b75a90460ed698c Mon Sep 17 00:00:00 2001 From: FUTURE <69786695+FUTURE-SL@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:19:37 +0300 Subject: [PATCH 207/224] perf: Optimizing interactions with CurrentItem (#681) Optimizing interactions with CurrentItem --- .../Features/DamageHandlers/CustomDamageHandler.cs | 12 +++++++++--- EXILED/Exiled.API/Features/Player.cs | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/CustomDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/CustomDamageHandler.cs index 67b464474f..fde6e9e00a 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/CustomDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/CustomDamageHandler.cs @@ -39,11 +39,17 @@ public CustomDamageHandler(Player target, BaseHandler baseHandler) if (Attacker is not null) { if (baseHandler is BaseScpDamageHandler) + { CustomBase = new ScpDamageHandler(target, baseHandler); - else if (Attacker.CurrentItem is not null && Attacker.CurrentItem.Type.IsWeapon() && baseHandler is BaseFirearmHandler) - CustomBase = new FirearmDamageHandler(Attacker.CurrentItem, target, baseHandler); + } else - CustomBase = new DamageHandler(target, Attacker); + { + Item item = Attacker.CurrentItem; + if (item is not null && item.Type.IsWeapon() && baseHandler is BaseFirearmHandler) + CustomBase = new FirearmDamageHandler(item, target, baseHandler); + else + CustomBase = new DamageHandler(target, Attacker); + } } else { diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 182215e1d8..e793a9164b 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2029,10 +2029,12 @@ public void DropItem(Item item, bool isThrown = false) /// Dropped item's . public Pickup DropHeldItem() { - if (CurrentItem is null) + Item item = CurrentItem; + + if (item is null) return null; - return DropItem(CurrentItem); + return DropItem(item); } /// From ced8516a29f8193d9f5b182783e754962aa10916 Mon Sep 17 00:00:00 2001 From: AlexInABox <36812824+AlexInABox@users.noreply.github.com> Date: Thu, 18 Dec 2025 19:44:12 +0100 Subject: [PATCH 208/224] fix: change release sorting from CreatedAt to PublishedAt (#692) * Change release sorting from CreatedAt to Id * Update EXILED/Exiled.Installer/Program.cs Co-authored-by: AlexInABox <36812824+AlexInABox@users.noreply.github.com> --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.Installer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.Installer/Program.cs b/EXILED/Exiled.Installer/Program.cs index 1924fc369d..6ea75b0df0 100644 --- a/EXILED/Exiled.Installer/Program.cs +++ b/EXILED/Exiled.Installer/Program.cs @@ -172,7 +172,7 @@ private static async Task> GetReleases() r => Version.TryParse(r.TagName, out Version version) && version > VersionLimit); - return releases.OrderByDescending(r => r.CreatedAt.Ticks); + return releases.OrderByDescending(r => r.PublishedAt); } private static string FormatRelease(Release r) From 73e59a8a0c4e91914652a2881ac50d994d949290 Mon Sep 17 00:00:00 2001 From: Yannik F <73497566+reversum@users.noreply.github.com> Date: Fri, 19 Dec 2025 01:09:22 +0100 Subject: [PATCH 209/224] fix: cassie MessageTranslated & replace obsolete "isHeld" (#691) * fix cassie MessageTranslated & remove obsolete "isHeld" * maybe use the translation with the split??? * Fix translation on ``Cassie:::MessageTranslated`` --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Cassie.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/EXILED/Exiled.API/Features/Cassie.cs b/EXILED/Exiled.API/Features/Cassie.cs index 42f0911ddd..b8664544a5 100644 --- a/EXILED/Exiled.API/Features/Cassie.cs +++ b/EXILED/Exiled.API/Features/Cassie.cs @@ -59,14 +59,7 @@ public static void Message(string message, bool isHeld = false, bool isNoisy = t /// Indicates whether C.A.S.S.I.E has to make subtitles. public static void MessageTranslated(string message, string translation, bool isHeld = false, bool isNoisy = true, bool isSubtitles = true) { - StringBuilder announcement = StringBuilderPool.Pool.Get(); - string[] cassies = message.Split('\n'); - string[] translations = translation.Split('\n'); - for (int i = 0; i < cassies.Length; i++) - announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} "); - - new CassieAnnouncement(new CassieTtsPayload(message, isSubtitles, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue(); - StringBuilderPool.Pool.Return(announcement); + new CassieAnnouncement(new CassieTtsPayload(message, translation, isHeld), 0f, isNoisy ? 1 : 0).AddToQueue(); } /// @@ -106,7 +99,15 @@ public static void DelayedGlitchyMessage(string message, float delay, float glit /// An obsolete parameter. /// Another obsolete parameter. /// Duration (in seconds) of specified message. - public static float CalculateDuration(string message, bool obsolete1, float obsolete2) + [Obsolete("Please use CalculateDuration(string)", true)] + public static float CalculateDuration(string message, bool obsolete1, float obsolete2) => CalculateDuration(message); + + /// + /// Calculates the duration of a C.A.S.S.I.E message. + /// + /// The message, which duration will be calculated. + /// Duration (in seconds) of specified message. + public static float CalculateDuration(string message) { if (!CassieTtsAnnouncer.TryGetDatabase(out CassieLineDatabase cassieLineDatabase)) { From a7290a44a2a85535b7bc9abb1e3b7229e4e7781b Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Fri, 19 Dec 2025 03:22:45 +0300 Subject: [PATCH 210/224] feat: Add Trygetbone helper method && 96 head transform (#682) * Update Exiled.API.csproj * Update FpcRole.cs * Fix indentation in FpcRole.cs * Update FpcRole.cs * Fix null checks for animator and avatar in FpcRole * Add HeadTransform property to Scp096Role Added HeadTransform property to retrieve SCP-096's head transform. * Update Scp096Role.cs --- EXILED/Exiled.API/Exiled.API.csproj | 1 + EXILED/Exiled.API/Features/Roles/FpcRole.cs | 24 +++++++++++++++++++ .../Exiled.API/Features/Roles/Scp096Role.cs | 18 +++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Exiled.API.csproj b/EXILED/Exiled.API/Exiled.API.csproj index 31fe36e002..70f81bf0d1 100644 --- a/EXILED/Exiled.API/Exiled.API.csproj +++ b/EXILED/Exiled.API/Exiled.API.csproj @@ -34,6 +34,7 @@ + diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 2b0d75cf00..9a1156833e 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -300,6 +300,30 @@ public BasicRagdoll Ragdoll /// public SpectatableModuleBase SpectatableModuleBase => FirstPersonController.SpectatorModule; + /// + /// Tries to get the of a specified bone. + /// + /// The bone to get the of. + /// + /// When this method returns, contains the of the specified bone, if found; + /// otherwise, null. + /// + /// true if the bone transform was found; otherwise, false. + public bool TryGetBoneTransform(HumanBodyBones bone, out Transform boneTransform) + { + boneTransform = null; + + if (Model is not AnimatedCharacterModel animatedModel) + return false; + + Animator animator = animatedModel.Animator; + if (animator == null || animator.avatar == null || !animator.avatar.isValid || !animator.avatar.isHuman) + return false; + + boneTransform = animator.GetBoneTransform(bone); + return boneTransform != null; + } + /// /// Resets the 's stamina. /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs index a59e699ac9..6e78b6c662 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp096Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp096Role.cs @@ -16,6 +16,8 @@ namespace Exiled.API.Features.Roles using PlayerRoles.PlayableScps.Scp096; using PlayerRoles.Subroutines; + using UnityEngine; + using Scp096GameRole = PlayerRoles.PlayableScps.Scp096.Scp096Role; /// @@ -139,6 +141,20 @@ internal Scp096Role(Scp096GameRole baseRole) /// public bool AttackPossible => AttackAbility.AttackPossible; + /// + /// Gets the head transform of SCP-096's character model. + /// + public Transform HeadTransform + { + get + { + if (Model is not Scp096CharacterModel scp96AnimatedCharacterModel) + return null; + + return scp96AnimatedCharacterModel.Head; + } + } + /// /// Gets or sets the Charge Ability Cooldown. /// @@ -306,4 +322,4 @@ public void Charge(float cooldown = 1f) /// The Spawn Chance. public float GetSpawnChance(List alreadySpawned) => Base.GetSpawnChance(alreadySpawned); } -} \ No newline at end of file +} From ea82de500483cd1bec4abeeb09e3b623fba12ce7 Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:31:28 +0300 Subject: [PATCH 211/224] feat: SCP-127 wrapper and events (#679) * feat: scp-127 wrapper * feat: scp-127 events * fix: I think this is not a problem * Replace Serial with Base for Scp127Item references * void Postfix is always better * Move pragma warning for SA1401 inside class --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../Features/Items/FirearmModules/Magazine.cs | 9 +- .../FirearmModules/Primary/Scp127Magazine.cs | 80 +++++ EXILED/Exiled.API/Features/Items/Item.cs | 12 +- EXILED/Exiled.API/Features/Items/Scp127.cs | 274 ++++++++++++++++++ .../EventArgs/Interfaces/IScp127Event.cs | 20 ++ .../Scp127/GainedExperienceEventArgs.cs | 51 ++++ .../Scp127/GainingExperienceEventArgs.cs | 59 ++++ .../EventArgs/Scp127/TalkedEventArgs.cs | 52 ++++ .../EventArgs/Scp127/TalkingEventArgs.cs | 57 ++++ EXILED/Exiled.Events/Events.cs | 10 + EXILED/Exiled.Events/Handlers/Scp127.cs | 81 ++++++ .../Patches/Generic/Scp127MaxHs.cs | 32 ++ 12 files changed, 732 insertions(+), 5 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Items/FirearmModules/Primary/Scp127Magazine.cs create mode 100644 EXILED/Exiled.API/Features/Items/Scp127.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Interfaces/IScp127Event.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp127/GainedExperienceEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp127/GainingExperienceEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp127/TalkedEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp127/TalkingEventArgs.cs create mode 100644 EXILED/Exiled.Events/Handlers/Scp127.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/Scp127MaxHs.cs diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs index 50a2538c64..8fb2688bb7 100644 --- a/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs +++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Magazine.cs @@ -11,9 +11,8 @@ namespace Exiled.API.Features.Items.FirearmModules using Exiled.API.Features.Items.FirearmModules.Barrel; using Exiled.API.Features.Items.FirearmModules.Primary; - using InventorySystem.Items.Firearms.Modules; - + using InventorySystem.Items.Firearms.Modules.Scp127; using UnityEngine; /// @@ -66,8 +65,12 @@ public static Magazine Get(IAmmoContainerModule module) PumpActionModule pump => new PumpBarrelMagazine(pump), IPrimaryAmmoContainerModule primary => primary switch { - MagazineModule magazine => new NormalMagazine(magazine), CylinderAmmoModule cylinder => new CylinderMagazine(cylinder), + MagazineModule magazine => magazine switch + { + Scp127MagazineModule scp127MagazineModule => new Scp127Magazine(scp127MagazineModule), + _ => new NormalMagazine(magazine) + }, _ => null, }, _ => null, diff --git a/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/Scp127Magazine.cs b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/Scp127Magazine.cs new file mode 100644 index 0000000000..c3226169ea --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/FirearmModules/Primary/Scp127Magazine.cs @@ -0,0 +1,80 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items.FirearmModules.Primary +{ + using InventorySystem.Items.Firearms.Modules.Scp127; + + /// + /// Represents a normal magazine for SCP-127. + /// + public class Scp127Magazine : NormalMagazine + { + /// + /// Initializes a new instance of the class. + /// + /// + public Scp127Magazine(Scp127MagazineModule magazine) + : base(magazine) + { + MagazineModule = magazine; + } + + /// + public new Scp127MagazineModule MagazineModule { get; } + + /// + /// Gets or sets the kill bonus. + /// + public int KillBonus + { + get => MagazineModule.KillBonus; + set => MagazineModule.KillBonus = value; + } + + /// + /// Gets or sets the rank up bonus. + /// + public int RankUpBonus + { + get => MagazineModule.RankUpBonus; + set => MagazineModule.RankUpBonus = value; + } + + /// + /// Gets or sets all settings. + /// + public Scp127MagazineModule.RegenerationSettings[] RegenerationPerTier + { + get => MagazineModule._regenerationPerTier; + set => MagazineModule._regenerationPerTier = value; + } + + /// + /// Gets the current setting. + /// + public Scp127MagazineModule.RegenerationSettings ActiveSetting => MagazineModule.ActiveSettings; + + /// + /// Gets or sets a pause in bullets regeneration process. + /// + public float RemainingRegenPause + { + get => MagazineModule._remainingRegenPause; + set => MagazineModule._remainingRegenPause = value; + } + + /// + /// Gets or sets the amount of bullets that should be regenerated. + /// + public float RegenProgress + { + get => MagazineModule._regenProgress; + set => MagazineModule._regenProgress = value; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index f9d8a74a0d..04d4b72535 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -218,7 +218,11 @@ public static Item Get(ItemBase itemBase) return itemBase switch { - InventorySystem.Items.Firearms.Firearm firearm => new Firearm(firearm), + InventorySystem.Items.Firearms.Firearm firearm => firearm.ItemTypeId switch + { + ItemType.GunSCP127 => new Scp127(firearm), + _ => new Firearm(firearm), + }, KeycardItem keycard => keycard switch { ChaosKeycardItem chaosKeycardItem => new ChaosKeycard(chaosKeycardItem), @@ -318,7 +322,11 @@ public static T Get(ushort serial) /// The created. This can be cast as a subclass. public static Item Create(ItemType type, Player owner = null) => type.GetTemplate() switch { - InventorySystem.Items.Firearms.Firearm => new Firearm(type), + InventorySystem.Items.Firearms.Firearm => type switch + { + ItemType.GunSCP127 => new Scp127(), + _ => new Firearm(type), + }, KeycardItem keycard => keycard switch { ChaosKeycardItem => new ChaosKeycard(type), diff --git a/EXILED/Exiled.API/Features/Items/Scp127.cs b/EXILED/Exiled.API/Features/Items/Scp127.cs new file mode 100644 index 0000000000..2c71fbe4bb --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Scp127.cs @@ -0,0 +1,274 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items +{ + using System.Collections.Generic; + using System.Linq; + + using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Firearms.Modules.Scp127; + using UnityEngine; + + /// + /// Represents SCP-127. + /// + public class Scp127 : Firearm + { + #pragma warning disable SA1401 + /// + /// Custom amount of max HS. + /// + internal float? CustomHsMax; + #pragma warning restore SA1401 + + /// + /// Initializes a new instance of the class. + /// + /// + public Scp127(InventorySystem.Items.Firearms.Firearm itemBase) + : base(itemBase) + { + foreach (ModuleBase module in Base.Modules) + { + switch (module) + { + case Scp127HumeModule humeModule: + HumeModule = humeModule; + break; + case Scp127TierManagerModule tierManagerModule: + TierManagerModule = tierManagerModule; + break; + case Scp127VoiceLineManagerModule voiceLineManagerModule: + VoiceLineManagerModule = voiceLineManagerModule; + break; + } + } + } + + /// + /// Initializes a new instance of the class. + /// + internal Scp127() + : this((InventorySystem.Items.Firearms.Firearm)Server.Host.Inventory.CreateItemInstance(new(ItemType.GunSCP127, 0), false)) + { + } + + /// + /// Gets a collection of all active HS sessions. + /// + public static List ActiveHumeShieldSessions => Scp127HumeModule.ServerActiveSessions; + + /// + /// Gets the instance. + /// + public Scp127HumeModule HumeModule { get; } + + /// + /// Gets the instance. + /// + public Scp127TierManagerModule TierManagerModule { get; } + + /// + /// Gets the instance. + /// + public Scp127VoiceLineManagerModule VoiceLineManagerModule { get; } + + /// + /// Gets or sets the maximum amount of HS. + /// + /// If setter is used, after tier chance this value won't be edited automatically. + public float HsMax + { + get => CustomHsMax ?? HumeModule.HsMax; + set => CustomHsMax = value; + } + + /// + /// Gets or sets a shield regeneration rate. + /// + public float ShieldRegenRate + { + get => HumeModule.ShieldRegenRate; + set => HumeModule.ShieldRegenRate = value; + } + + /// + /// Gets or sets a shield decay time. + /// + public float ShieldDecayRate + { + get => HumeModule.ShieldDecayRate; + set => HumeModule.ShieldDecayRate = value; + } + + /// + /// Gets or sets a pause before HS starts regeneration after damage being taken. + /// + public float ShieldOnDamagePause + { + get => HumeModule.ShieldOnDamagePause; + set => HumeModule.ShieldOnDamagePause = value; + } + + /// + /// Gets or sets a delay before HS starts dropping after unequipment. + /// + public float UnequipDecayDelay + { + get => HumeModule.UnequipDecayDelay; + set => HumeModule.UnequipDecayDelay = value; + } + + /// + /// Gets or sets a HumeShield regeneration. + /// + public float HsRegeneration + { + get => HumeModule.HsRegeneration; + set => HumeModule.HsRegeneration = value; + } + + /// + /// Gets or sets an experience bonus for kill. + /// + public float KillBonus + { + get => TierManagerModule.KillBonus; + set => TierManagerModule.KillBonus = value; + } + + /// + /// Gets or sets an amount of passive experience increase. + /// + public float PassiveExpAmount + { + get => TierManagerModule.PassiveExpAmount; + set => TierManagerModule.PassiveExpAmount = value; + } + + /// + /// Gets or sets an interval of passive experience increase. + /// + public float PassiveExpInterval + { + get => TierManagerModule.PassiveExpInterval; + set => TierManagerModule.PassiveExpInterval = value; + } + + /// + /// Gets or sets a collection of all tier thresholds. + /// + public Scp127TierManagerModule.TierThreshold[] TierThresholds + { + get => TierManagerModule.Thresholds; + set => TierManagerModule.Thresholds = value; + } + + /// + /// Gets or sets current tier. + /// + public Scp127Tier CurrentTier + { + get => TierManagerModule.CurTier; + set => TierManagerModule.CurTier = value; + } + + /// + /// Gets or sets the current experience amount. + /// + public float Experience + { + get => TierManagerModule.ServerExp; + set => TierManagerModule.ServerExp = value; + } + + /// + /// Gets the instance record. + /// + public Scp127TierManagerModule.InstanceRecord InstanceRecord => Scp127TierManagerModule.GetRecord(Serial); + + /// + /// Gets the Owner stats. + /// + public Scp127TierManagerModule.OwnerStats OwnerStats => Scp127TierManagerModule.GetStats(Base); + + /// + /// Gets or sets all Voice Triggers. + /// + public Scp127VoiceTriggerBase[] VoiceTriggers + { + get => VoiceLineManagerModule._foundTriggers; + set => VoiceLineManagerModule._foundTriggers = value; + } + + /// + /// Gets a collection of players that are friends with this SCP-127. + /// + public IEnumerable Friends + { + get + { + if (!Scp127VoiceLineManagerModule.FriendshipMemory.TryGetValue(Serial, out HashSet uintSet)) + return null; + + return uintSet.Select(Player.Get); + } + } + + /// + /// Increases experience. + /// + /// Amount to add. + public void IncreaseExperience(float amount) => TierManagerModule.ServerIncreaseExp(Base, amount); + + /// + /// Sets owner stats. + /// + /// New experience amount. + public void SetOwnerStats(float exp) => TierManagerModule.ServerSetStats(exp); + + /// + /// Sends tier stats. + /// + /// New tier. + /// New progress. + public void SendTierStats(Scp127Tier tier, byte progress) => TierManagerModule.ServerSendStats(Serial, Owner.ReferenceHub, tier, progress); + + /// + /// Tries to play voice line. + /// + /// Voice line to play. + /// Priority of the play. + /// true if voice line has been played successfully. Otherwise, false. + public bool TryPlayVoiceLine(Scp127VoiceLinesTranslation voiceLine, Scp127VoiceTriggerBase.VoiceLinePriority priority = Scp127VoiceTriggerBase.VoiceLinePriority.Normal) + { + if (!VoiceLineManagerModule.TryFindVoiceLine(voiceLine, out Scp127VoiceTriggerBase triggerBase, out AudioClip audioClip)) + return false; + + VoiceLineManagerModule.ServerSendVoiceLine(triggerBase, null, audioClip, (byte)priority); + return true; + } + + /// + /// Checks if this instance of SCP-127 and have friendship. + /// + /// Target to check. + /// true if this instance of SCP-127 and have friendship. Otherwise, false. + public bool HasFriendship(Player player) => Scp127VoiceLineManagerModule.HasFriendship(Serial, player.ReferenceHub); + + /// + /// Adds player as a friend. + /// + /// Target to be added. + public void AddFriend(Player player) + { + HashSet uints = Scp127VoiceLineManagerModule.FriendshipMemory.GetOrAddNew(Serial); + uints.Add(player.NetId); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp127Event.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp127Event.cs new file mode 100644 index 0000000000..616ad8f3e5 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp127Event.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Interfaces +{ + /// + /// Represents all events related to SCP-127. + /// + public interface IScp127Event : IItemEvent + { + /// + /// Gets the SCP-127 instance, related to this event. + /// + public API.Features.Items.Scp127 Scp127 { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp127/GainedExperienceEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp127/GainedExperienceEventArgs.cs new file mode 100644 index 0000000000..533ccbb5ce --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp127/GainedExperienceEventArgs.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp127 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Firearms.Modules.Scp127; + + /// + /// Contains all information before SCP-127 gains experience. + /// + public class GainedExperienceEventArgs : IScp127Event + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public GainedExperienceEventArgs(Scp127 scp127, float experience) + { + Scp127 = scp127; + Experience = experience; + Tier = Scp127.TierManagerModule.GetTierForExp(Experience + Scp127.Experience); + } + + /// + public Player Player => Scp127.Owner; + + /// + public Item Item => Scp127; + + /// + public Scp127 Scp127 { get; } + + /// + /// Gets the experience that SCP-127 has gained. + /// + public float Experience { get; } + + /// + /// Gets the tier. + /// + public Scp127Tier Tier { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp127/GainingExperienceEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp127/GainingExperienceEventArgs.cs new file mode 100644 index 0000000000..6a1d7b738b --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp127/GainingExperienceEventArgs.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.Scp127 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Firearms.Modules.Scp127; + + /// + /// Contains all information before SCP-127 gains experience. + /// + public class GainingExperienceEventArgs : IScp127Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public GainingExperienceEventArgs(Scp127 scp127, float experience, bool isAllowed = true) + { + Scp127 = scp127; + Experience = experience; + IsAllowed = isAllowed; + } + + /// + public Player Player => Scp127.Owner; + + /// + public Item Item => Scp127; + + /// + public Scp127 Scp127 { get; } + + /// + public bool IsAllowed { get; set; } + + /// + /// Gets or sets the gaining experience. + /// + public float Experience { get; set; } + + /// + /// Gets or sets the new tier. + /// + public Scp127Tier Tier + { + get => Scp127.TierManagerModule.GetTierForExp(Experience + Scp127.Experience); + set => Experience = Scp127.TierManagerModule.GetExpForTier(value) - Scp127.Experience; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp127/TalkedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp127/TalkedEventArgs.cs new file mode 100644 index 0000000000..10810403d5 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp127/TalkedEventArgs.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.Scp127 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Firearms.Modules.Scp127; + + /// + /// Contains all information after SCP-127 voice line is played. + /// + public class TalkedEventArgs : IScp127Event + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TalkedEventArgs(Scp127 scp127, Scp127VoiceLinesTranslation voiceLine, Scp127VoiceTriggerBase.VoiceLinePriority voiceLinePriority) + { + Scp127 = scp127; + VoiceLine = voiceLine; + Priority = voiceLinePriority; + } + + /// + public Player Player => Scp127.Owner; + + /// + public Item Item => Scp127; + + /// + public Scp127 Scp127 { get; } + + /// + /// Gets a voice line which is played. + /// + public Scp127VoiceLinesTranslation VoiceLine { get; } + + /// + /// Gets a priority for this play. + /// + public Scp127VoiceTriggerBase.VoiceLinePriority Priority { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp127/TalkingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp127/TalkingEventArgs.cs new file mode 100644 index 0000000000..b8934566a8 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp127/TalkingEventArgs.cs @@ -0,0 +1,57 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp127 +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Firearms.Modules.Scp127; + + /// + /// Contains all information before SCP-127 voice line is played. + /// + public class TalkingEventArgs : IScp127Event, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public TalkingEventArgs(Scp127 scp127, Scp127VoiceLinesTranslation voiceLine, Scp127VoiceTriggerBase.VoiceLinePriority voiceLinePriority, bool isAllowed = true) + { + Scp127 = scp127; + VoiceLine = voiceLine; + Priority = voiceLinePriority; + IsAllowed = isAllowed; + } + + /// + public Player Player => Scp127.Owner; + + /// + public Item Item => Scp127; + + /// + public Scp127 Scp127 { get; } + + /// + public bool IsAllowed { get; set; } + + /// + /// Gets or sets a voice line which is played. + /// + public Scp127VoiceLinesTranslation VoiceLine { get; set; } + + /// + /// Gets or sets a priority for this play. + /// + public Scp127VoiceTriggerBase.VoiceLinePriority Priority { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 1645fe20a1..2bffa6a8f9 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -92,6 +92,11 @@ public override void OnEnabled() LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon += Handlers.Player.OnReloadingWeapon; LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon += Handlers.Player.OnUnloadingWeapon; + LabApi.Events.Handlers.Scp127Events.Talking += Handlers.Scp127.OnTalking; + LabApi.Events.Handlers.Scp127Events.Talked += Handlers.Scp127.OnTalked; + LabApi.Events.Handlers.Scp127Events.GainingExperience += Handlers.Scp127.OnGainingExperience; + LabApi.Events.Handlers.Scp127Events.GainExperience += Handlers.Scp127.OnGainedExperience; + ServerConsole.ReloadServerName(); } @@ -129,6 +134,11 @@ public override void OnDisabled() LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon -= Handlers.Player.OnReloadingWeapon; LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon -= Handlers.Player.OnUnloadingWeapon; + + LabApi.Events.Handlers.Scp127Events.Talking -= Handlers.Scp127.OnTalking; + LabApi.Events.Handlers.Scp127Events.Talked -= Handlers.Scp127.OnTalked; + LabApi.Events.Handlers.Scp127Events.GainingExperience -= Handlers.Scp127.OnGainingExperience; + LabApi.Events.Handlers.Scp127Events.GainExperience -= Handlers.Scp127.OnGainedExperience; } /// diff --git a/EXILED/Exiled.Events/Handlers/Scp127.cs b/EXILED/Exiled.Events/Handlers/Scp127.cs new file mode 100644 index 0000000000..b3f86534d3 --- /dev/null +++ b/EXILED/Exiled.Events/Handlers/Scp127.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Handlers +{ + using Exiled.Events.EventArgs.Scp127; + using Exiled.Events.Features; + using LabApi.Events.Arguments.Scp127Events; + +#pragma warning disable SA1623 + /// + /// SCP-127 event handlers. + /// + public static class Scp127 + { + /// + /// Invoked before SCP-127 voice line is played. + /// + public static Event Talking { get; set; } = new(); + + /// + /// Invoked after SCP-127 voice line is played. + /// + public static Event Talked { get; set; } = new(); + + /// + /// Invoked before SCP-127 gains experience. + /// + public static Event GainingExperience { get; set; } = new(); + + /// + /// Invoked after SCP-127 gains experience. + /// + public static Event GainedExperience { get; set; } = new(); + + /// + /// Called before SCP-127 voice line is played. + /// + /// The instance. + public static void OnTalking(Scp127TalkingEventArgs ev) + { + TalkingEventArgs eventArgs = new(API.Features.Items.Item.Get(ev.Scp127Item.Base), ev.VoiceLine, ev.Priority, ev.IsAllowed); + Talking.InvokeSafely(eventArgs); + + ev.Priority = eventArgs.Priority; + ev.VoiceLine = eventArgs.VoiceLine; + ev.IsAllowed = eventArgs.IsAllowed; + } + + /// + /// Called after SCP-127 voice line is played. + /// + /// The instance. + public static void OnTalked(Scp127TalkedEventArgs ev) + => Talked.InvokeSafely(new(API.Features.Items.Item.Get(ev.Scp127Item.Base), ev.VoiceLine, ev.Priority)); + + /// + /// Called before SCP-127 gains experience. + /// + /// The instance. + public static void OnGainingExperience(Scp127GainingExperienceEventArgs ev) + { + GainingExperienceEventArgs eventArgs = new(API.Features.Items.Item.Get(ev.Scp127Item.Base), ev.ExperienceGain, ev.IsAllowed); + GainingExperience.InvokeSafely(eventArgs); + + ev.ExperienceGain = eventArgs.Experience; + ev.IsAllowed = eventArgs.IsAllowed; + } + + /// + /// Called after SCP-127 gains experience. + /// + /// The instance. + public static void OnGainedExperience(Scp127GainExperienceEventArgs ev) => + GainedExperience.InvokeSafely(new(API.Features.Items.Item.Get(ev.Scp127Item.Base), ev.ExperienceGain)); + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp127MaxHs.cs b/EXILED/Exiled.Events/Patches/Generic/Scp127MaxHs.cs new file mode 100644 index 0000000000..c98d2d57c2 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/Scp127MaxHs.cs @@ -0,0 +1,32 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using Exiled.API.Features.Items; + using HarmonyLib; + using InventorySystem.Items.Firearms.Modules.Scp127; + +#pragma warning disable SA1313 + /// + /// Patches to implement setter. + /// + [HarmonyPatch(typeof(Scp127HumeModule), nameof(Scp127HumeModule.HsMax), MethodType.Getter)] + internal class Scp127MaxHs + { + private static void Postfix(Scp127HumeModule __instance, ref float __result) + { + Scp127 item = Item.Get(__instance.ItemSerial); + + if (item == null || !item.CustomHsMax.HasValue) + return; + + __result = item.CustomHsMax.Value; + return; + } + } +} \ No newline at end of file From e3d0f973dc96c0b896c5b95dc5dca6d7393796af Mon Sep 17 00:00:00 2001 From: Pawmi <75163547+Pawmii@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:19:47 +0200 Subject: [PATCH 212/224] =?UTF-8?q?fix:=20TPS=20calculation=20ignoring=20s?= =?UTF-8?q?erver=E2=80=99s=20max=20tickrate=20(#656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: TPS calculation ignoring server’s max tickrate * Server::SmoothTps * Update Tps summary to clarify average value --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Server.cs | 22 ++++++++++++++++++++- EXILED/Exiled.Events/Commands/TpsCommand.cs | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 96b8311fdf..825cfb3566 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -111,7 +111,27 @@ public static string Name /// /// Gets the actual ticks per second of the server. /// - public static double Tps => Math.Round(1f / Time.smoothDeltaTime); + public static double Tps + { + get + { + double delta = Time.deltaTime; + + if (delta <= 0) + return MaxTps; + + double tps = 1d / delta; + + tps = Math.Min(tps, MaxTps); + + return tps; + } + } + + /// + /// Gets the average ticks per second of the server. + /// + public static double SmoothTps => Math.Round(1f / Time.smoothDeltaTime); /// /// Gets or sets the max ticks per second of the server. diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Events/Commands/TpsCommand.cs index 722de3b61e..bca8efdf6b 100644 --- a/EXILED/Exiled.Events/Commands/TpsCommand.cs +++ b/EXILED/Exiled.Events/Commands/TpsCommand.cs @@ -31,7 +31,7 @@ public class TpsCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) { - double diff = Server.Tps / Server.MaxTps; + double diff = Server.SmoothTps / Server.MaxTps; string color = diff switch { > 0.9 => "green", @@ -39,7 +39,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s _ => "red" }; - response = $"{Server.Tps}/{Server.MaxTps}"; + response = $"{Server.SmoothTps}/{Server.MaxTps}"; return true; } } From aa6ca400e145a5e928abff2a7a0114bcd6024dc0 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:27:02 -0500 Subject: [PATCH 213/224] feat: 14.2.4 Fixes (#693) * Patch and compilation fixes * Enum shenanigans * Remove all xmas obsoletes REVERT THIS AFTER THE UPDATE! * 3 new effects * Update documenation & RoleExtensions * Fix for black candy bug * Rename enums, fix a prefab * lil accidents --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Enums/CameraType.cs | 3 ++ EXILED/Exiled.API/Enums/DoorType.cs | 5 +++ EXILED/Exiled.API/Enums/EffectType.cs | 23 ++++++++-- EXILED/Exiled.API/Enums/GlassType.cs | 5 +++ EXILED/Exiled.API/Enums/PrefabType.cs | 42 ++++++++++++++++++ EXILED/Exiled.API/Enums/ProjectileType.cs | 6 +-- EXILED/Exiled.API/Enums/RoomType.cs | 5 +++ EXILED/Exiled.API/Enums/Side.cs | 5 +++ .../Extensions/EffectTypeExtension.cs | 3 ++ .../Exiled.API/Extensions/RoleExtensions.cs | 9 ++-- EXILED/Exiled.API/Features/Camera.cs | 3 ++ EXILED/Exiled.API/Features/Doors/Door.cs | 1 + .../Exiled.API/Features/Roles/Scp1507Role.cs | 2 +- EXILED/Exiled.API/Features/Room.cs | 1 + EXILED/Exiled.API/Features/Scp559.cs | 24 +++++++--- EXILED/Exiled.API/Features/Window.cs | 1 + EXILED/Exiled.Events/Commands/TpsCommand.cs | 1 - .../EventArgs/Interfaces/IScp1507Event.cs | 2 +- .../EventArgs/Interfaces/IScp559Event.cs | 2 +- .../Scp1507/AttackingDoorEventArgs.cs | 2 +- .../EventArgs/Scp1507/ScreamingEventArgs.cs | 2 +- .../Scp1507/SpawningFlamingosEventArgs.cs | 2 +- .../EventArgs/Scp1507/UsingTapeEventArgs.cs | 2 +- .../Scp559/InteractingScp559EventArgs.cs | 2 +- .../EventArgs/Scp559/SpawningEventArgs.cs | 2 +- EXILED/Exiled.Events/Events.cs | 1 + .../Exiled.Events/Handlers/Internal/Round.cs | 8 ++++ EXILED/Exiled.Events/Handlers/Scp1507.cs | 2 +- EXILED/Exiled.Events/Handlers/Scp559.cs | 2 +- .../Patches/Events/Scp2536/GrantingGift.cs | 44 ++++++++++++++++++- EXILED/Exiled.Loader/AutoUpdateFiles.cs | 2 +- .../SCPSLRessources/NW_Documentation.md | 19 +++++--- 32 files changed, 198 insertions(+), 35 deletions(-) diff --git a/EXILED/Exiled.API/Enums/CameraType.cs b/EXILED/Exiled.API/Enums/CameraType.cs index 1a193d16db..292da90b2d 100644 --- a/EXILED/Exiled.API/Enums/CameraType.cs +++ b/EXILED/Exiled.API/Enums/CameraType.cs @@ -161,6 +161,9 @@ public enum CameraType EzGateBSide, EzGateAStairwell, EzGateAUpper, + HczLoadingBay, + HczLoadingBayRamp, + HczLoadingBayStairwell, #endregion } } diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index 87e0de99a5..5863d6e79a 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -369,5 +369,10 @@ public enum DoorType /// Represents the door in the Armory. /// GateAArmory, + + /// + /// Represents the door in . + /// + HczLoadingBay, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index e70e273e77..4e7c6056d6 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -263,25 +263,25 @@ public enum EffectType /// /// Makes you a flamingo. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] BecomingFlamingo, /// /// Makes you a Child after eating Cake. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] Scp559, /// /// Scp956 found you. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] Scp956Target, /// /// you are snowed. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] Snowed, /// @@ -390,5 +390,20 @@ public enum EffectType /// . /// Scp1509Resurrected, + + /// + /// . + /// + FocusedVision, + + /// + /// . + /// + AnomalousRegeneration, + + /// + /// . + /// + AnomalousTarget, } } diff --git a/EXILED/Exiled.API/Enums/GlassType.cs b/EXILED/Exiled.API/Enums/GlassType.cs index 85c068108f..fef4d59f44 100644 --- a/EXILED/Exiled.API/Enums/GlassType.cs +++ b/EXILED/Exiled.API/Enums/GlassType.cs @@ -84,5 +84,10 @@ public enum GlassType /// Represents the window in . /// GateAArmory, + + /// + /// Represents the window in + /// + HczLoadingBay, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index 6a52ce7995..43936f1d80 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -417,5 +417,47 @@ public enum PrefabType [Prefab(213466224, "SCP-939 Ragdoll Halloween")] Scp939RagdollHalloween, + + [Prefab(689741320, "SCP-559 Cake")] + Scp559Cake, + + [Prefab(2657863153, "SCP-956")] + Scp956, + + [Prefab(1205960739, "SCP-2536 Tree")] + Scp2536, + + [Prefab(2102014206, "Snowpile")] + Snowpile, + + [Prefab(3401975113, "Scp018Projectile Christmas")] + Scp018ProjectileChristmas, + + [Prefab(3223468476, "SnowballProjectile")] + SnowballProjectile, + + [Prefab(296717882, "CoalPickup")] + CoalPickup, + + [Prefab(409273101, "TapePlayerPickup")] + Scp1507TapePickup, + + [Prefab(3971391978, "Scp021JPickup")] + Scp021JPickup, + + [Prefab(142820664, "CoalProjectile")] + CoalProjectile, + + [Prefab(2405470903, "Scp2536Projectile")] + Scp2536Projectile, + + [Prefab(1496232901, "SCP-173 Ragdoll Variant")] + Scp173RagdollChristmas, + + [Prefab(6069361, "SnowPoop - TantrumObj")] + SnowTantrum, + + [Prefab(3654754970, "SCP-1507 Ragdoll")] + Scp1507Ragdoll, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/ProjectileType.cs b/EXILED/Exiled.API/Enums/ProjectileType.cs index a8072ed48e..2d95210924 100644 --- a/EXILED/Exiled.API/Enums/ProjectileType.cs +++ b/EXILED/Exiled.API/Enums/ProjectileType.cs @@ -51,21 +51,21 @@ public enum ProjectileType /// Coal. /// Used by . /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] Coal, /// /// SpecialCoal. /// Used by . /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] SpecialCoal, /// /// Snowball. /// Used by . /// - [Obsolete("Only availaible for Christmas.")] + // [Obsolete("Only availaible for Christmas.")] Snowball, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/RoomType.cs b/EXILED/Exiled.API/Enums/RoomType.cs index 0b7737c193..6dceeea6e2 100644 --- a/EXILED/Exiled.API/Enums/RoomType.cs +++ b/EXILED/Exiled.API/Enums/RoomType.cs @@ -353,5 +353,10 @@ public enum RoomType /// Heavy Containment Zone's straight hall room with lava. /// HczDss12 = HczIncineratorWayside, + + /// + /// Heavy Containment Zone's T-intersection with a ramp in it. + /// + HczLoadingBay, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Enums/Side.cs b/EXILED/Exiled.API/Enums/Side.cs index 5e9c063be7..5accace2f3 100644 --- a/EXILED/Exiled.API/Enums/Side.cs +++ b/EXILED/Exiled.API/Enums/Side.cs @@ -52,5 +52,10 @@ public enum Side /// No team. Same as . /// None, + + /// + /// Contains and . Same as . + /// + Flamingos, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index c44a32362d..088438e085 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -101,6 +101,9 @@ public static class EffectTypeExtension { EffectType.TraumatizedByEvil, typeof(TraumatizedByEvil) }, { EffectType.WhiteCandy, typeof(WhiteCandy) }, { EffectType.Scp1509Resurrected, typeof(Scp1509Resurrected) }, + { EffectType.FocusedVision, typeof(FocusedVision) }, + { EffectType.AnomalousRegeneration, typeof(AnomalousRegeneration) }, + { EffectType.AnomalousTarget, typeof(AnomalousTarget) }, #pragma warning restore CS0618 }); diff --git a/EXILED/Exiled.API/Extensions/RoleExtensions.cs b/EXILED/Exiled.API/Extensions/RoleExtensions.cs index 369910654a..db1a7c99a9 100644 --- a/EXILED/Exiled.API/Extensions/RoleExtensions.cs +++ b/EXILED/Exiled.API/Extensions/RoleExtensions.cs @@ -70,6 +70,7 @@ public static class RoleExtensions Team.FoundationForces or Team.Scientists => Side.Mtf, Team.ChaosInsurgency or Team.ClassD => Side.ChaosInsurgency, Team.OtherAlive => Side.Tutorial, + Team.Flamingos => Side.Flamingos, _ => Side.None, }; @@ -80,12 +81,13 @@ public static class RoleExtensions /// . public static Team GetTeam(this RoleTypeId roleType) => roleType switch { - RoleTypeId.ChaosConscript or RoleTypeId.ChaosMarauder or RoleTypeId.ChaosRepressor or RoleTypeId.ChaosRifleman => Team.ChaosInsurgency, + RoleTypeId.ChaosConscript or RoleTypeId.ChaosMarauder or RoleTypeId.ChaosRepressor or RoleTypeId.ChaosRifleman or RoleTypeId.ChaosFlamingo => Team.ChaosInsurgency, RoleTypeId.Scientist => Team.Scientists, RoleTypeId.ClassD => Team.ClassD, - RoleTypeId.Scp049 or RoleTypeId.Scp939 or RoleTypeId.Scp0492 or RoleTypeId.Scp079 or RoleTypeId.Scp096 or RoleTypeId.Scp106 or RoleTypeId.Scp173 or RoleTypeId.Scp3114 => Team.SCPs, - RoleTypeId.FacilityGuard or RoleTypeId.NtfCaptain or RoleTypeId.NtfPrivate or RoleTypeId.NtfSergeant or RoleTypeId.NtfSpecialist => Team.FoundationForces, + RoleTypeId.Scp049 or RoleTypeId.Scp939 or RoleTypeId.Scp0492 or RoleTypeId.Scp079 or RoleTypeId.Scp096 or RoleTypeId.Scp106 or RoleTypeId.Scp173 or RoleTypeId.Scp3114 or RoleTypeId.ZombieFlamingo=> Team.SCPs, + RoleTypeId.FacilityGuard or RoleTypeId.NtfCaptain or RoleTypeId.NtfPrivate or RoleTypeId.NtfSergeant or RoleTypeId.NtfSpecialist or RoleTypeId.NtfFlamingo => Team.FoundationForces, RoleTypeId.Tutorial => Team.OtherAlive, + RoleTypeId.Flamingo or RoleTypeId.AlphaFlamingo => Team.Flamingos, _ => Team.Dead, }; @@ -131,6 +133,7 @@ public static bool TryGetRoleBase(this RoleTypeId roleType, out T roleBase) Team.ClassD or Team.ChaosInsurgency => LeadingTeam.ChaosInsurgency, Team.FoundationForces or Team.Scientists => LeadingTeam.FacilityForces, Team.SCPs => LeadingTeam.Anomalies, + Team.Flamingos => LeadingTeam.Flamingo, _ => LeadingTeam.Draw, }; diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index 4601a5ada1..fa17a77540 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -145,6 +145,9 @@ public class Camera : IWrapper, IWorldSpace ["GATE B SIDE"] = CameraType.EzGateBSide, ["GATE A STAIRWELL"] = CameraType.EzGateAStairwell, ["GATE A UPPER"] = CameraType.EzGateAUpper, + ["LOADING BAY"] = CameraType.HczLoadingBay, + ["HCZ LOADING RAMP"] = CameraType.HczLoadingBayRamp, + ["STAIRWELL"] = CameraType.HczLoadingBayStairwell, // CustomCamera ["EZ ARM CAMERA TOY"] = CameraType.EzArmCameraToy, diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 19718900a7..a8163f207c 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -624,6 +624,7 @@ private DoorType GetDoorType() RoomType.HczEzCheckpointA => DoorType.CheckpointArmoryA, RoomType.HczEzCheckpointB => DoorType.CheckpointArmoryB, RoomType.EzGateA => DoorType.GateAArmory, + RoomType.HczLoadingBay => DoorType.HczLoadingBay, _ => DoorType.UnknownDoor, }, "Unsecured Pryable GateDoor" => Room?.Type switch diff --git a/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs b/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs index a4c9ba78cf..170bda116a 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp1507Role.cs @@ -20,7 +20,7 @@ namespace Exiled.API.Features.Roles /// /// Defines a role that represents SCP-1507. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class Scp1507Role : FpcRole, ISubroutinedScpRole, IHumeShieldRole, ISpawnableScp { /// diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs index a81a24173f..846a76509c 100644 --- a/EXILED/Exiled.API/Features/Room.cs +++ b/EXILED/Exiled.API/Features/Room.cs @@ -506,6 +506,7 @@ private static RoomType FindType(GameObject gameObject) "HCZ_ChkpB" => RoomType.HczElevatorB, "HCZ_127" => RoomType.Hcz127, "HCZ_ServerRoom" => RoomType.HczServerRoom, + "HCZ_Intersection_Ramp" => RoomType.HczLoadingBay, "EZ_GateA" => RoomType.EzGateA, "EZ_GateB" => RoomType.EzGateB, "EZ_ThreeWay" => RoomType.EzTCross, diff --git a/EXILED/Exiled.API/Features/Scp559.cs b/EXILED/Exiled.API/Features/Scp559.cs index 08fc899047..d05702b351 100644 --- a/EXILED/Exiled.API/Features/Scp559.cs +++ b/EXILED/Exiled.API/Features/Scp559.cs @@ -18,7 +18,7 @@ namespace Exiled.API.Features /// /// Represents a cake. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class Scp559 : IWrapper, IPosition { /// @@ -50,20 +50,34 @@ public Scp559(Scp559Cake cakeBase) /// /// Gets a with spawnpoint in rooms. /// - public static Dictionary SpawnPositions => Scp559Cake.Spawnpoints; + public static Dictionary SpawnPositions { get; } = Scp559Spawnpoint.InstancesPerRoom.ToDictionary(kvp => kvp.Key.Name, kvp => + { + Transform roomTransform = kvp.Key.transform; + Transform spawnTransform = kvp.Value.transform; + + Vector3 localPos = roomTransform.InverseTransformPoint(spawnTransform.position); + + return new Vector4(localPos.x, localPos.y, localPos.z, spawnTransform.eulerAngles.y); + }); /// /// Gets the list of all available spawnpoints. /// - public static List AvailableSpawnpoints => Scp559Cake.PossiblePositions; + public static List AvailableSpawnpoints => Scp559Cake.PossibleSpawnpoints.Select(spawnpoint => + { + Transform t = spawnpoint.transform; + return new Vector4(t.position.x, t.position.y, t.position.z, t.eulerAngles.y); + }).ToList(); /// /// Gets or sets offset for spawning near pedestals. /// public static Vector3 PedestalOffset { - get => Scp559Cake.PedestalOffset; - set => Scp559Cake.PedestalOffset.Set(value.x, value.y, value.z); + get => new(0, -Scp559Spawnpoint.PedestalHeight, 0); + + [Obsolete("Setter no longer works")] + set { } } /// diff --git a/EXILED/Exiled.API/Features/Window.cs b/EXILED/Exiled.API/Features/Window.cs index e5a1624e0d..2d327ff0c4 100644 --- a/EXILED/Exiled.API/Features/Window.cs +++ b/EXILED/Exiled.API/Features/Window.cs @@ -231,6 +231,7 @@ public void DamageWindow(float amount, DamageHandlerBase handler) RoomType.HczEzCheckpointB => GlassType.HczEzCheckpointB, RoomType.EzGateA when Base.name[7] == '5' => GlassType.GateAArmory, RoomType.EzGateA => GlassType.GateAPit, + RoomType.HczLoadingBay => GlassType.HczLoadingBay, _ => GlassType.Unknown, }, "Window" => Room?.Type switch diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Events/Commands/TpsCommand.cs index bca8efdf6b..14c0def05e 100644 --- a/EXILED/Exiled.Events/Commands/TpsCommand.cs +++ b/EXILED/Exiled.Events/Commands/TpsCommand.cs @@ -15,7 +15,6 @@ namespace Exiled.Events.Commands /// /// Command for showing current server TPS. /// - [CommandHandler(typeof(RemoteAdminCommandHandler))] [CommandHandler(typeof(GameConsoleCommandHandler))] public class TpsCommand : ICommand { diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp1507Event.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp1507Event.cs index 1d6dcf3add..acb2463878 100644 --- a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp1507Event.cs +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp1507Event.cs @@ -14,7 +14,7 @@ namespace Exiled.Events.EventArgs.Interfaces /// /// Event args used for all related events. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public interface IScp1507Event : IPlayerEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp559Event.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp559Event.cs index d8a5537257..fc62383340 100644 --- a/EXILED/Exiled.Events/EventArgs/Interfaces/IScp559Event.cs +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IScp559Event.cs @@ -12,7 +12,7 @@ namespace Exiled.Events.EventArgs.Interfaces /// /// Defines the base contract for all related events. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public interface IScp559Event : IExiledEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp1507/AttackingDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1507/AttackingDoorEventArgs.cs index c58330e252..c6ea947e55 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp1507/AttackingDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp1507/AttackingDoorEventArgs.cs @@ -18,7 +18,7 @@ namespace Exiled.Events.EventArgs.Scp1507 /// /// Contains all information before SCP-1507 attacks door. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class AttackingDoorEventArgs : IScp1507Event, IDeniableEvent, IDoorEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp1507/ScreamingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1507/ScreamingEventArgs.cs index ae5ebb8450..7ab56f98e2 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp1507/ScreamingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp1507/ScreamingEventArgs.cs @@ -16,7 +16,7 @@ namespace Exiled.Events.EventArgs.Scp1507 /// /// Contains all information before SCP-1507 screams. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class ScreamingEventArgs : IScp1507Event, IDeniableEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp1507/SpawningFlamingosEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1507/SpawningFlamingosEventArgs.cs index 376eba5abf..5cc50f9352 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp1507/SpawningFlamingosEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp1507/SpawningFlamingosEventArgs.cs @@ -19,7 +19,7 @@ namespace Exiled.Events.EventArgs.Scp1507 /// /// Contains all information before flamingos get spawned. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class SpawningFlamingosEventArgs : IDeniableEvent, IPlayerEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp1507/UsingTapeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp1507/UsingTapeEventArgs.cs index 0121d02d95..10b713052e 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp1507/UsingTapeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp1507/UsingTapeEventArgs.cs @@ -17,7 +17,7 @@ namespace Exiled.Events.EventArgs.Scp1507 /// /// Contains all information before SCP-1507 screams. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class UsingTapeEventArgs : IPlayerEvent, IItemEvent, IDeniableEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp559/InteractingScp559EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp559/InteractingScp559EventArgs.cs index b8b99f9ac9..c05c444bf0 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp559/InteractingScp559EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp559/InteractingScp559EventArgs.cs @@ -15,7 +15,7 @@ namespace Exiled.Events.EventArgs.Scp559 /// /// Contains all information before a player interacts with SCP-559. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class InteractingScp559EventArgs : IScp559Event, IDeniableEvent, IPlayerEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp559/SpawningEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp559/SpawningEventArgs.cs index 6e3a81f06a..c83baddd2c 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp559/SpawningEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp559/SpawningEventArgs.cs @@ -16,7 +16,7 @@ namespace Exiled.Events.EventArgs.Scp559 /// /// Contains all information before SCP-559 spawns. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public class SpawningEventArgs : IDeniableEvent, IScp559Event { /// diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 2bffa6a8f9..c941abd7ca 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -72,6 +72,7 @@ public override void OnEnabled() Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense; Handlers.Player.Verified += Handlers.Internal.Round.OnVerified; Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; + Handlers.Warhead.Detonated += Handlers.Internal.Round.OnWarheadDetonated; RoleAssigner.OnPlayersSpawned += Handlers.Server.OnAllPlayersSpawned; CharacterClassManager.OnRoundStarted += Handlers.Server.OnRoundStarted; diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 3ce8660bbe..f7c3b6f4ea 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -28,6 +28,7 @@ namespace Exiled.Events.Handlers.Internal using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp244.Hypothermia; + using InventorySystem.Items.Usables.Scp330; using PlayerRoles; using PlayerRoles.FirstPersonControl; using PlayerRoles.RoleAssign; @@ -124,6 +125,13 @@ public static void OnVerified(VerifiedEventArgs ev) } } + /// + public static void OnWarheadDetonated() + { + // fix for black candy + CandyBlack.Outcomes.RemoveAll(outcome => outcome is TeleportOutcome); + } + private static void GenerateAttachments() { foreach (FirearmType firearmType in EnumUtils.Values) diff --git a/EXILED/Exiled.Events/Handlers/Scp1507.cs b/EXILED/Exiled.Events/Handlers/Scp1507.cs index 63e4536d2c..f36b16d633 100644 --- a/EXILED/Exiled.Events/Handlers/Scp1507.cs +++ b/EXILED/Exiled.Events/Handlers/Scp1507.cs @@ -17,7 +17,7 @@ namespace Exiled.Events.Handlers /// /// SCP-1507 related events. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public static class Scp1507 { /// diff --git a/EXILED/Exiled.Events/Handlers/Scp559.cs b/EXILED/Exiled.Events/Handlers/Scp559.cs index b17f520c9d..70d5387520 100644 --- a/EXILED/Exiled.Events/Handlers/Scp559.cs +++ b/EXILED/Exiled.Events/Handlers/Scp559.cs @@ -17,7 +17,7 @@ namespace Exiled.Events.Handlers /// /// All SCP-559 related events. /// - [Obsolete("Only availaible for Christmas and AprilFools.")] + // [Obsolete("Only availaible for Christmas and AprilFools.")] public static class Scp559 { /// diff --git a/EXILED/Exiled.Events/Patches/Events/Scp2536/GrantingGift.cs b/EXILED/Exiled.Events/Patches/Events/Scp2536/GrantingGift.cs index 9389addfa5..855523f1e3 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp2536/GrantingGift.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp2536/GrantingGift.cs @@ -11,6 +11,7 @@ namespace Exiled.Events.Patches.Events.Scp2536 using System.Reflection.Emit; using Christmas.Scp2536; + using Christmas.Scp2536.Gifts; using Exiled.API.Features; using Exiled.API.Features.Pools; using Exiled.Events.Attributes; @@ -34,6 +35,7 @@ private static IEnumerable Transpiler(IEnumerable x.LoadsField(Field(typeof(Scp2536GiftBase), nameof(Scp2536GiftBase.ObtainedBy)))) + offset; @@ -45,7 +47,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable instruction.Calls(Method(typeof(Scp2536GiftController), nameof(Scp2536GiftController.ServerGetGift), null, new[] { typeof(Naughty) }))) + offset; + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // naughty is already on the stack + // naughty = this.ServerGetGift(); + new(OpCodes.Stloc_S, naughty), + + // Player.Get(hub); + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // naughty + new(OpCodes.Ldloc_S, naughty), + + // true + new(OpCodes.Ldc_I4_1), + + // GrantingGiftEventArgs ev = new(Player, Scp2536GiftBase, true); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(GrantingGiftEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Scp2536.OnGrantingGift(ev); + new(OpCodes.Call, Method(typeof(Handlers.Scp2536), nameof(Handlers.Scp2536.OnGrantingGift))), + + // if (!ev.IsAllowed) + // goto retLabel; + new(OpCodes.Callvirt, PropertyGetter(typeof(GrantingGiftEventArgs), nameof(GrantingGiftEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + + // load naughty onto stack + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(GrantingGiftEventArgs), nameof(GrantingGiftEventArgs.Gift))), }); newInstructions[newInstructions.Count - 1].labels.Add(retLabel); diff --git a/EXILED/Exiled.Loader/AutoUpdateFiles.cs b/EXILED/Exiled.Loader/AutoUpdateFiles.cs index 132afb6e22..3ad56c5c95 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, 2, 0, 3); + public static readonly Version RequiredSCPSLVersion = new(14, 2, 0, 4); } } \ No newline at end of file diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index 12ddd043ad..faeda9462e 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.2.0.3) +Last Update (14.2.0.4) ### Index @@ -2772,6 +2772,7 @@ Last Update (14.2.0.3) [66] = KeycardCustomMetalCase [67] = MarshmallowItem [68] = SCP1509 + [69] = Scp021J [-1] = None ``` @@ -4080,6 +4081,8 @@ Last Update (14.2.0.3) [25] = Flamingo [26] = AlphaFlamingo [27] = ZombieFlamingo + [28] = NtfFlamingo + [29] = ChaosFlamingo [-1] = None ``` @@ -4143,6 +4146,7 @@ Last Update (14.2.0.3) [38] = Hcz127 [39] = HczAcroamaticAbatement [40] = HczWaysideIncinerator + [41] = HczRampTunnel ```
@@ -5540,6 +5544,8 @@ Last Update (14.2.0.3) [3] = Two [4] = Three [5] = Four + [6] = Five + [7] = Six ```
@@ -5718,6 +5724,7 @@ Last Update (14.2.0.3) [5] = Scp127Voice [6] = Scp3114Voice [7] = ChaseThemes + [8] = SoundEffectsWhileSpectating ``` @@ -5826,9 +5833,11 @@ Last Update (14.2.0.3) | 22 | Filmmaker | Dead | None | Draw | | 23 | Scp3114 | SCPs | Scp | Anomalies | | 24 | Destroyed | Dead | None | Draw | -| 25 | Flamingo | Dead | None | Draw | -| 26 | AlphaFlamingo | Dead | None | Draw | -| 27 | ZombieFlamingo | Dead | None | Draw | +| 25 | Flamingo | Flamingos | Flamingos | Flamingo | +| 26 | AlphaFlamingo | Flamingos | Flamingos | Flamingo | +| 27 | ZombieFlamingo | SCPs | Scp | Anomalies | +| 28 | NtfFlamingo | FoundationForces | Mtf | FacilityForces | +| 29 | ChaosFlamingo | ChaosInsurgency | ChaosInsurgency | ChaosInsurgency | ``` @@ -5839,7 +5848,7 @@ Last Update (14.2.0.3)
Damage Handlers -```md title="Latest Updated: 14.2.0.3" +```md title="Latest Updated: 14.2.0.4" All available DamageHandlers + Symbol ':' literally means "inherits from" From 7a27dc16a11b2f953108c8a74e6c54dbeabf8ef6 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:48:28 -0500 Subject: [PATCH 214/224] Version bump --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index b1e03a2c89..add2a791d6 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.11.2 + 9.11.3 false From 16be76034b374359c9a442c81115a97a7fcd17ab Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:33:08 +0100 Subject: [PATCH 215/224] fix: FixMarshmallowManFF (#694) * FixMarshmallowManFF * fix DamageType.Marshmallow * new stuff added by NW --- .../Extensions/DamageTypeExtensions.cs | 7 ++ .../DamageHandlers/DamageHandlerBase.cs | 7 ++ .../Patches/Fixes/FixMarshmallowManFF.cs | 99 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 95dfc33727..6fde63477d 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -204,6 +204,13 @@ public static DamageType GetDamageType(DamageHandlerBase damageHandlerBase) Log.Warn($"{nameof(DamageTypeExtensions)}.{nameof(damageHandlerBase)}: No matching {nameof(DamageType)} for {nameof(UniversalDamageHandler)} with ID {translation.Id}, type will be reported as {DamageType.Unknown}. Report this to EXILED Devs."); return DamageType.Unknown; } + + case AttackerDamageHandler attackerDamageHandler: + { + if (Player.TryGet(attackerDamageHandler.Attacker, out Player attacker) && attacker.CurrentItem?.Type == ItemType.MarshmallowItem) + return DamageType.Marshmallow; + return DamageType.Unknown; + } } return DamageType.Unknown; diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs index ec59d5ee5a..248e89452b 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs @@ -271,6 +271,13 @@ protected DamageType GetDamageType(BaseHandler damageHandler = null) FirearmType.FRMG0 => DamageType.Frmg0, _ => DamageType.Firearm }; + + case PlayerStatsSystem.AttackerDamageHandler attackerDamageHandler: + { + if (Player.TryGet(attackerDamageHandler.Attacker, out Player attacker) && attacker.CurrentItem?.Type == ItemType.MarshmallowItem) + return DamageType.Marshmallow; + return DamageType.Unknown; + } } return DamageType.Unknown; diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs new file mode 100644 index 0000000000..d91c2d0398 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs @@ -0,0 +1,99 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using CustomPlayerEffects; + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using Footprinting; + using HarmonyLib; + using InventorySystem.Items.MarshmallowMan; + using InventorySystem.Items.Pickups; + using InventorySystem.Items.ThrowableProjectiles; + using Mirror; + using PlayerRoles; + using PlayerStatsSystem; + using Respawning.NamingRules; + using Subtitles; + + using static HarmonyLib.AccessTools; + + /// + /// Patches the delegate. + /// + [HarmonyPatch(typeof(MarshmallowItem), nameof(MarshmallowItem.ServerAttack))] + internal class FixMarshmallowManFF : AttackerDamageHandler + { +#pragma warning disable SA1600 // Elements should be documented + public FixMarshmallowManFF(MarshmallowItem marshmallowItem) + { + Attacker = new(marshmallowItem.Owner); + Damage = marshmallowItem._attackDamage; + AllowSelfDamage = false; + ServerLogsText = "MarshmallowManFF Fix"; + } + + public override Footprint Attacker { get; set; } + + public override bool AllowSelfDamage { get; } + + public override float Damage { get; set; } + + public override string RagdollInspectText { get; } + + public override CassieAnnouncement CassieDeathAnnouncement + { + get + { + return new CassieAnnouncement() + { + Announcement = "TERMINATED BY MARSHMALLOW MAN", + SubtitleParts = new SubtitlePart[] + { + new SubtitlePart(SubtitleType.TerminatedByMarshmallowMan, null), + }, + }; + } + } + + public override string DeathScreenText { get; } + + public override string ServerLogsText { get; } +#pragma warning restore SA1600 // Elements should be documented +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + + private static bool Prefix(MarshmallowItem __instance, ReferenceHub syncTarget) + { + foreach (IDestructible destructible in __instance.DetectDestructibles()) + { + HitboxIdentity hitboxIdentity = destructible as HitboxIdentity; + if ((hitboxIdentity == null || (!(hitboxIdentity.TargetHub != syncTarget) && (__instance.EvilMode || HitboxIdentity.IsDamageable(__instance.Owner, hitboxIdentity.TargetHub)))) && destructible.Damage(__instance._attackDamage, new FixMarshmallowManFF(__instance), destructible.CenterOfMass)) + { + HitboxIdentity hitboxIdentity2 = destructible as HitboxIdentity; + if (hitboxIdentity2 != null && !hitboxIdentity2.TargetHub.IsAlive()) + __instance.Owner.playerEffectsController.GetEffect().OnKill(); + + if (__instance.EvilMode) + __instance.EvilAHPProcess.CurrentAmount += 100f; + + Hitmarker.SendHitmarkerDirectly(__instance.Owner, 1f, true); + __instance.ServerSendPublicRpc(writer => + { + writer.WriteByte(1); + }); + break; + } + } + + return false; + } + } +} From 8c0195569eaefd46442daca628411d39332cff58 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:02:38 -0500 Subject: [PATCH 216/224] feat: More christmas events (#695) * FoundPosition event * Add marshmallow man events * Rewrite Yamato's prefix as transpiler * simplify marshmallow event patches, add IL comments * Fix Marshmallow creation and add parameters to Cackle also fix patch I borked from last commit lol * unobsolete EffectType.Marshmallow --- EXILED/Exiled.API/Enums/EffectType.cs | 2 +- EXILED/Exiled.API/Features/Items/Item.cs | 3 + .../Exiled.API/Features/Items/Marshmallow.cs | 102 ++++++++++++++++++ .../EventArgs/Interfaces/IMarshmallowEvent.cs | 22 ++++ .../EventArgs/Item/CacklingEventArgs.cs | 51 +++++++++ .../EventArgs/Item/PunchingEventArgs.cs | 51 +++++++++ .../Scp2536/FoundPositionEventArgs.cs | 40 +++++++ EXILED/Exiled.Events/Handlers/Item.cs | 22 ++++ EXILED/Exiled.Events/Handlers/Scp2536.cs | 11 ++ .../Patches/Events/Item/Cackling.cs | 69 ++++++++++++ .../Patches/Events/Item/Punching.cs | 69 ++++++++++++ .../Patches/Events/Scp2536/FoundPosition.cs | 86 +++++++++++++++ .../Patches/Fixes/FixMarshmallowManFF.cs | 61 ++++------- 13 files changed, 549 insertions(+), 40 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Items/Marshmallow.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Interfaces/IMarshmallowEvent.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp2536/FoundPositionEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Item/Punching.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp2536/FoundPosition.cs diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index 4e7c6056d6..44c5b02301 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -216,7 +216,7 @@ public enum EffectType /// /// Makes you a marshmallow guy. /// - [Obsolete("Not functional in-game")] + // [Obsolete("Not functional in-game")] Marshmallow, /// diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 04d4b72535..89bb189c42 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -22,6 +22,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Firearms.Ammo; using InventorySystem.Items.Jailbird; using InventorySystem.Items.Keycards; + using InventorySystem.Items.MarshmallowMan; using InventorySystem.Items.MicroHID; using InventorySystem.Items.Pickups; using InventorySystem.Items.Radio; @@ -260,6 +261,7 @@ public static Item Get(ItemBase itemBase) _ => new Throwable(throwable), }, Scp1509Item scp1509 => new Scp1509(scp1509), + MarshmallowItem marshmallow => new Marshmallow(marshmallow), _ => new(itemBase), }; } @@ -364,6 +366,7 @@ public static T Get(ushort serial) _ => new Throwable(type, owner), }, Scp1509Item => new Scp1509(), + MarshmallowItem => new Marshmallow(type, owner), _ => new(type), }; diff --git a/EXILED/Exiled.API/Features/Items/Marshmallow.cs b/EXILED/Exiled.API/Features/Items/Marshmallow.cs new file mode 100644 index 0000000000..05b8579cfe --- /dev/null +++ b/EXILED/Exiled.API/Features/Items/Marshmallow.cs @@ -0,0 +1,102 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Items +{ + using CustomPlayerEffects; + using Exiled.API.Interfaces; + using InventorySystem.Items.MarshmallowMan; + using PlayerStatsSystem; + using UnityEngine; + + /// + /// A wrapper class for . + /// + public class Marshmallow : Item, IWrapper + { + /// + /// Initializes a new instance of the class. + /// + /// The base class. + public Marshmallow(MarshmallowItem itemBase) + : base(itemBase) + { + Base = itemBase; + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the marshmallow item. + /// The owner of the marshmallow item. Leave for no owner. + internal Marshmallow(ItemType type, Player owner = null) + : base((MarshmallowItem)(owner ?? Server.Host).Inventory.CreateItemInstance(new(type, 0), false)) + { + } + + /// + /// Gets the that this class is encapsulating. + /// + public new MarshmallowItem Base { get; } + + /// + /// Gets a value indicating whether this marshmallow man is evil. + /// + /// See in regards to making a marshmallow evil. + public bool Evil => Base.EvilMode; + + /// + /// Gets or sets the of the marshmallow man that would be used if he was evil. + /// + public AhpStat.AhpProcess EvilAhpProcess + { + get => Base.EvilAHPProcess; + set + { + if (Evil && value is null) + return; + + Base.EvilAHPProcess = value; + } + } + + /// + /// Cackles for the owner even if they are not evil. + /// + /// How long until the player can cackle again (negative values do not affect current cooldown). + /// How long players near the marshmallow man get effected by . + public void Cackle(double cooldown = -1, float duration = 5) + { + if (cooldown >= 0) + Base._cackleCooldown.Trigger(cooldown); + + Base.ServerSendPublicRpc(writer => + { + writer.WriteByte(4); + Base._cackleCooldown.WriteCooldown(writer); + }); + + foreach (Player player in Player.List) + { + if (Vector3.Distance(player.Position, Owner.Position) <= 5F && player.CurrentItem is not Marshmallow { Evil: true }) + player.EnableEffect(duration); + } + } + + /// + /// Makes the owner of this marshmallow evil. You CANNOT undo this without resetting the player. + /// + /// The of the new evil player. + public void MakeEvil(AhpStat.AhpProcess evilProcess = null) + { + if (Evil) + return; + + Base.ReleaseEvil(evilProcess ?? EvilAhpProcess ?? new AhpStat.AhpProcess(450F, 450F, 0F, 1F, 0F, true)); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Interfaces/IMarshmallowEvent.cs b/EXILED/Exiled.Events/EventArgs/Interfaces/IMarshmallowEvent.cs new file mode 100644 index 0000000000..fdd41cef5b --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Interfaces/IMarshmallowEvent.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Interfaces +{ + using Exiled.API.Features.Items; + + /// + /// Represents all events related to the marshmallow man. + /// + public interface IMarshmallowEvent : IItemEvent + { + /// + /// Gets the marshmallow item related to this event. + /// + public Marshmallow Marshmallow { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs new file mode 100644 index 0000000000..69150fd226 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// 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.MarshmallowMan; + + /// + /// Contains all information before a marshmallow man punches. + /// + public class CacklingEventArgs : IMarshmallowEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The Player cackling. + /// The marshmallow item of the player cackling. + /// Whether the player is allowed to cackle. + public CacklingEventArgs(Player player, MarshmallowItem marshmallow, bool isAllowed = true) + { + Player = player; + Marshmallow = Item.Get(marshmallow); + IsAllowed = isAllowed; + } + + /// + /// Gets the player cackling. + /// + public Player Player { get; } + + /// + public API.Features.Items.Item Item => Marshmallow; + + /// + /// Gets the marshmallow item of the player cackling. + /// + public Marshmallow Marshmallow { get; } + + /// + /// Gets or sets a value indicating whether the player is allowed to cackle. + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs new file mode 100644 index 0000000000..d616d34e93 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// 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.MarshmallowMan; + + /// + /// Contains all information before a marshmallow man punches. + /// + public class PunchingEventArgs : IMarshmallowEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The Player attacking. + /// The marshmallow item of the player attacking. + /// Whether the player is allowed to punch. + public PunchingEventArgs(Player player, MarshmallowItem marshmallow, bool isAllowed = true) + { + Player = player; + Marshmallow = Item.Get(marshmallow); + IsAllowed = isAllowed; + } + + /// + /// Gets the player punching. + /// + public Player Player { get; } + + /// + public Item Item => Marshmallow; + + /// + /// Gets the marshmallow item of the player punching. + /// + public Marshmallow Marshmallow { get; } + + /// + /// Gets or sets a value indicating whether the punch is allowed. + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp2536/FoundPositionEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp2536/FoundPositionEventArgs.cs new file mode 100644 index 0000000000..d4935e6c92 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp2536/FoundPositionEventArgs.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp2536 +{ + using Christmas.Scp2536; + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information after SCP-2536 chooses target for spawning. + /// + public class FoundPositionEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public FoundPositionEventArgs(Player player, Scp2536Spawnpoint spawnpoint) + { + Player = player; + Spawnpoint = spawnpoint; + } + + /// + /// Gets the player near whom SCP-2536 will spawn. + /// + public Player Player { get; } + + /// + /// Gets or sets the spawn point where SCP will spawn. + /// + public Scp2536Spawnpoint Spawnpoint { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index 9ac83aafd3..b1ca257f09 100644 --- a/EXILED/Exiled.Events/Handlers/Item.cs +++ b/EXILED/Exiled.Events/Handlers/Item.cs @@ -89,6 +89,16 @@ public static class Item /// public static Event JailbirdChangedWearState { get; set; } = new(); + /// + /// Invoked before a marshmallow man punches. + /// + public static Event Punching { get; set; } = new(); + + /// + /// Invoked before a marshmallow man cackles. + /// + public static Event Cackling { get; set; } = new(); + /// /// Called before the Jailbird's is changed. /// @@ -173,5 +183,17 @@ public static class Item ///
/// The instance. public static void OnInspectedItem(InspectedItemEventArgs ev) => InspectedItem.InvokeSafely(ev); + + /// + /// Called before a marshmallow man punches. + /// + /// The instance. + public static void OnPunching(PunchingEventArgs ev) => Punching.InvokeSafely(ev); + + /// + /// Called before a marshmallow man cackles. + /// + /// The instance. + public static void OnCackling(CacklingEventArgs ev) => Cackling.InvokeSafely(ev); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Scp2536.cs b/EXILED/Exiled.Events/Handlers/Scp2536.cs index 3f7dfd1c58..8c0d272583 100644 --- a/EXILED/Exiled.Events/Handlers/Scp2536.cs +++ b/EXILED/Exiled.Events/Handlers/Scp2536.cs @@ -21,6 +21,11 @@ public static class Scp2536 ///
public static Event FindingPosition { get; set; } = new(); + /// + /// Invoked once SCP-2536 has found a spawn location. + /// + public static Event FoundPosition { get; set; } = new(); + /// /// Invoked before SCP-2536 gives a gift to a player. /// @@ -37,6 +42,12 @@ public static class Scp2536 /// The instance. public static void OnFindingPosition(FindingPositionEventArgs ev) => FindingPosition.InvokeSafely(ev); + /// + /// Called after SCP-2536 chooses a target. + /// + /// The instance. + public static void OnFoundPosition(FoundPositionEventArgs ev) => FoundPosition.InvokeSafely(ev); + /// /// Called before SCP-2536 gives a gift to a player. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs b/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs new file mode 100644 index 0000000000..9c71dd86c6 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Item +{ + 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.Item; + using HarmonyLib; + using InventorySystem.Items.MarshmallowMan; + + using static HarmonyLib.AccessTools; + + /// + /// Patch the . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.Cackling))] + [HarmonyPatch(typeof(MarshmallowItem), nameof(MarshmallowItem.ServerProcessCackle))] + public class Cackling + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retLabel = generator.DefineLabel(); + + int offset = 1; + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + offset; + + newInstructions.InsertRange(index, new[] + { + // player = Player.Get(Owner); + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Callvirt, PropertyGetter(typeof(MarshmallowItem), nameof(MarshmallowItem.Owner))), + + // item = this; + new(OpCodes.Ldarg_0), + + // true + new(OpCodes.Ldc_I4_1), + + // ev = new CacklingEventArgs(player, item, true); + new(OpCodes.Newobj, Constructor(typeof(CacklingEventArgs), new[] { typeof(Player), typeof(MarshmallowItem), typeof(bool) })), + new(OpCodes.Dup), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnCackling))), + + // if (!ev.IsAllowed) return + new(OpCodes.Callvirt, PropertyGetter(typeof(CacklingEventArgs), nameof(CacklingEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + }); + + newInstructions[newInstructions.Count - 1].WithLabels(retLabel); + + 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/Punching.cs b/EXILED/Exiled.Events/Patches/Events/Item/Punching.cs new file mode 100644 index 0000000000..e45e0bfe76 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Item/Punching.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Item +{ + 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.Item; + using HarmonyLib; + using InventorySystem.Items.MarshmallowMan; + + using static HarmonyLib.AccessTools; + + /// + /// Patch the . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.Punching))] + [HarmonyPatch(typeof(MarshmallowItem), nameof(MarshmallowItem.ServerProcessCmd))] + public class Punching + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retLabel = generator.DefineLabel(); + + int offset = 1; + int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + offset; + + newInstructions[^1].WithLabels(retLabel); + + newInstructions.InsertRange(index, new[] + { + // player = Player.Get(Owner); + new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]), + new(OpCodes.Callvirt, PropertyGetter(typeof(MarshmallowItem), nameof(MarshmallowItem.Owner))), + + // item = this; + new(OpCodes.Ldarg_0), + + // true + new(OpCodes.Ldc_I4_1), + + // ev = new PunchingEventArgs(player, item, true); + new(OpCodes.Newobj, Constructor(typeof(PunchingEventArgs), new[] { typeof(Player), typeof(MarshmallowItem), typeof(bool) })), + new(OpCodes.Dup), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnPunching))), + + // if (!ev.IsAllowed) return + new(OpCodes.Callvirt, PropertyGetter(typeof(PunchingEventArgs), nameof(PunchingEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, retLabel), + }); + + 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/Scp2536/FoundPosition.cs b/EXILED/Exiled.Events/Patches/Events/Scp2536/FoundPosition.cs new file mode 100644 index 0000000000..dd942042fb --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp2536/FoundPosition.cs @@ -0,0 +1,86 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp2536 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Christmas.Scp2536; + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp2536; + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add event. + /// + [EventPatch(typeof(Handlers.Scp2536), nameof(Handlers.Scp2536.FoundPosition))] + [HarmonyPatch(typeof(Scp2536Controller), nameof(Scp2536Controller.ServerFindTarget))] + public class FoundPosition + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retLabel = generator.DefineLabel(); + + int index = newInstructions.Count - 1; + + newInstructions[index].WithLabels(retLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // dupe the result (true / false if found) + new(OpCodes.Dup), + + // return w result if failed + new(OpCodes.Brfalse_S, retLabel), + new(OpCodes.Pop), + + // load ADDRESS of parameter "out Scp2536Spawnpoint spawnpoint" onto stack (so not actual value but address) + // this is used at end of transpiler + new(OpCodes.Ldarg_2), + + // load address of parameter "out ReferenceHub target" + new(OpCodes.Ldarg_1), + + // load value at address + new(OpCodes.Ldind_Ref), + + // player = Player.Get(target); + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // load address of parameter "out Scp2536Spawnpoint spawnpoint" + new(OpCodes.Ldarg_2), + + // load value at address + new(OpCodes.Ldind_Ref), + + // ev = new FoundPositionEventArgs(player, spawnpoint); + new(OpCodes.Newobj, Constructor(typeof(FoundPositionEventArgs), new[] { typeof(Player), typeof(Scp2536Spawnpoint) })), + new(OpCodes.Dup), + new(OpCodes.Call, Method(typeof(Handlers.Scp2536), nameof(Handlers.Scp2536.OnFoundPosition))), + + // spawnpoint = ev.Spawnpoint; + new(OpCodes.Callvirt, PropertyGetter(typeof(FoundPositionEventArgs), nameof(FoundPositionEventArgs.Spawnpoint))), + + // this uses the address already on stack (see 3rd comment) with the value from the event args + new(OpCodes.Stind_Ref), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs index d91c2d0398..bdfc2e5f0d 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs @@ -35,7 +35,7 @@ internal class FixMarshmallowManFF : AttackerDamageHandler #pragma warning disable SA1600 // Elements should be documented public FixMarshmallowManFF(MarshmallowItem marshmallowItem) { - Attacker = new(marshmallowItem.Owner); + Attacker = new Footprint(marshmallowItem.Owner); Damage = marshmallowItem._attackDamage; AllowSelfDamage = false; ServerLogsText = "MarshmallowManFF Fix"; @@ -47,53 +47,36 @@ public FixMarshmallowManFF(MarshmallowItem marshmallowItem) public override float Damage { get; set; } - public override string RagdollInspectText { get; } + public override string RagdollInspectText { get; } = DeathTranslations.MarshmallowMan.RagdollTranslation; - public override CassieAnnouncement CassieDeathAnnouncement + public override CassieAnnouncement CassieDeathAnnouncement { get; } = new() { - get - { - return new CassieAnnouncement() - { - Announcement = "TERMINATED BY MARSHMALLOW MAN", - SubtitleParts = new SubtitlePart[] - { - new SubtitlePart(SubtitleType.TerminatedByMarshmallowMan, null), - }, - }; - } - } + Announcement = "TERMINATED BY MARSHMALLOW MAN", + SubtitleParts = + [ + new SubtitlePart(SubtitleType.TerminatedByMarshmallowMan, null), + ], + }; - public override string DeathScreenText { get; } + public override string DeathScreenText { get; } = DeathTranslations.MarshmallowMan.DeathscreenTranslation; public override string ServerLogsText { get; } #pragma warning restore SA1600 // Elements should be documented #pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private static bool Prefix(MarshmallowItem __instance, ReferenceHub syncTarget) + private static IEnumerable Transpiler(IEnumerable instructions) { - foreach (IDestructible destructible in __instance.DetectDestructibles()) - { - HitboxIdentity hitboxIdentity = destructible as HitboxIdentity; - if ((hitboxIdentity == null || (!(hitboxIdentity.TargetHub != syncTarget) && (__instance.EvilMode || HitboxIdentity.IsDamageable(__instance.Owner, hitboxIdentity.TargetHub)))) && destructible.Damage(__instance._attackDamage, new FixMarshmallowManFF(__instance), destructible.CenterOfMass)) - { - HitboxIdentity hitboxIdentity2 = destructible as HitboxIdentity; - if (hitboxIdentity2 != null && !hitboxIdentity2.TargetHub.IsAlive()) - __instance.Owner.playerEffectsController.GetEffect().OnKill(); - - if (__instance.EvilMode) - __instance.EvilAHPProcess.CurrentAmount += 100f; - - Hitmarker.SendHitmarkerDirectly(__instance.Owner, 1f, true); - __instance.ServerSendPublicRpc(writer => - { - writer.WriteByte(1); - }); - break; - } - } - - return false; + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindIndex(instruction => instruction.Calls(PropertyGetter(typeof(MarshmallowItem), nameof(MarshmallowItem.NewDamageHandler)))); + + // replace the getter for NewDamageHandler with ctor of FixMarshmallowManFF + newInstructions[index] = new CodeInstruction(OpCodes.Newobj, Constructor(typeof(FixMarshmallowManFF), new[] { typeof(MarshmallowItem) })); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); } } } From 14b57f101bda2af37249bea3cccc0b58394b8cab Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:13:27 -0500 Subject: [PATCH 217/224] I forgor (version bump) --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index add2a791d6..901d77fdcd 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.11.3 + 9.12.0 false From 81de237edc66c5814fe90809931530ae9db29d5f Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:52:07 +0100 Subject: [PATCH 218/224] Fix isEvilMode for "FixMarshmallowManFF" --- .../Patches/Fixes/FixMarshmallowManFF.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs index bdfc2e5f0d..0f29e2c863 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/FixMarshmallowManFF.cs @@ -33,10 +33,11 @@ namespace Exiled.Events.Patches.Fixes internal class FixMarshmallowManFF : AttackerDamageHandler { #pragma warning disable SA1600 // Elements should be documented - public FixMarshmallowManFF(MarshmallowItem marshmallowItem) + public FixMarshmallowManFF(MarshmallowItem marshmallowItem, bool isEvilMode) { - Attacker = new Footprint(marshmallowItem.Owner); + Attacker = new(marshmallowItem.Owner); Damage = marshmallowItem._attackDamage; + IsFriendlyFire = isEvilMode; AllowSelfDamage = false; ServerLogsText = "MarshmallowManFF Fix"; } @@ -71,7 +72,13 @@ private static IEnumerable Transpiler(IEnumerable instruction.Calls(PropertyGetter(typeof(MarshmallowItem), nameof(MarshmallowItem.NewDamageHandler)))); // replace the getter for NewDamageHandler with ctor of FixMarshmallowManFF - newInstructions[index] = new CodeInstruction(OpCodes.Newobj, Constructor(typeof(FixMarshmallowManFF), new[] { typeof(MarshmallowItem) })); + newInstructions.RemoveAt(index); + newInstructions.InsertRange(index, new List + { + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(MarshmallowItem), nameof(MarshmallowItem.EvilMode))), + new(OpCodes.Newobj, Constructor(typeof(FixMarshmallowManFF), new[] { typeof(MarshmallowItem) })), + }); for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; From a00c3770e739394a1cda05c7faeccf3ae13e74e3 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Sun, 21 Dec 2025 10:29:22 -0500 Subject: [PATCH 219/224] Fix ChaosKeycard::SnakeEngine --- EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs b/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs index 8d8bdad39d..bab50d19ce 100644 --- a/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs +++ b/EXILED/Exiled.API/Features/Items/Keycards/ChaosKeycard.cs @@ -43,7 +43,8 @@ internal ChaosKeycard(ItemType type) /// /// Gets the this encapsulates. /// - public SnakeEngine SnakeEngine => Base._localEngine; + /// Can be null, but shouldn't be during usage. + public SnakeEngine SnakeEngine => ChaosKeycardItem.SnakeSessions.TryGetValue(Serial, out SnakeEngine engine) ? engine : null; /// /// Returns the Keycard in a human readable format. From 342609a8be3f4cdef1cfe8d340395db8e1747e5e Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:04:49 +0100 Subject: [PATCH 220/224] fix: CacklingEventArgs & PunchingEventArgs and the FixMarshmallowManCustomDamageHandler (#696) * fix: CacklingEventArgs & PunchingEventArgs * fix: BugFix for FixMarshmallowManFF * fix: finish fix for FixMarshmallowManFF * minor doc fix --------- Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> --- .../EventArgs/Item/CacklingEventArgs.cs | 7 +++---- .../EventArgs/Item/PunchingEventArgs.cs | 7 +++---- EXILED/Exiled.Events/Handlers/Internal/Round.cs | 3 +++ .../Exiled.Events/Patches/Events/Item/Cackling.cs | 12 ++++-------- .../Exiled.Events/Patches/Events/Item/Punching.cs | 12 ++++-------- .../Patches/Fixes/FixMarshmallowManFF.cs | 14 ++++++++------ 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs index 69150fd226..8e491ee0b1 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/CacklingEventArgs.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -20,13 +20,12 @@ public class CacklingEventArgs : IMarshmallowEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// The Player cackling. /// The marshmallow item of the player cackling. /// Whether the player is allowed to cackle. - public CacklingEventArgs(Player player, MarshmallowItem marshmallow, bool isAllowed = true) + public CacklingEventArgs(MarshmallowItem marshmallow, bool isAllowed = true) { - Player = player; Marshmallow = Item.Get(marshmallow); + Player = Marshmallow.Owner; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs index d616d34e93..d2d927d95a 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/PunchingEventArgs.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -20,13 +20,12 @@ public class PunchingEventArgs : IMarshmallowEvent, IDeniableEvent /// /// Initializes a new instance of the class. /// - /// The Player attacking. /// The marshmallow item of the player attacking. /// Whether the player is allowed to punch. - public PunchingEventArgs(Player player, MarshmallowItem marshmallow, bool isAllowed = true) + public PunchingEventArgs(MarshmallowItem marshmallow, bool isAllowed = true) { - Player = player; Marshmallow = Item.Get(marshmallow); + Player = Marshmallow.Owner; IsAllowed = isAllowed; } diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index f7c3b6f4ea..3da3e94ef1 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -91,6 +91,9 @@ public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { if (ev.Role.IsDead() || !ev.Role.IsFpcRole()) ev.IsAllowed = false; + + if (ev.DamageHandlerBase is Exiled.Events.Patches.Fixes.FixMarshmallowManFF fixMarshamllowManFf) + ev.DamageHandlerBase = fixMarshamllowManFf.MarshmallowItem.NewDamageHandler; } /// diff --git a/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs b/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs index 9c71dd86c6..fec7811ecf 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/Cackling.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -38,18 +38,14 @@ private static IEnumerable Transpiler(IEnumerable // Copyright (c) ExMod Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. @@ -40,18 +40,14 @@ private static IEnumerable Transpiler(IEnumerable "Stabbed with Marshmallow Item by " + Attacker.Nickname; #pragma warning restore SA1600 // Elements should be documented #pragma warning disable SA1313 // Parameter names should begin with lower-case letter @@ -77,7 +79,7 @@ private static IEnumerable Transpiler(IEnumerable Date: Sun, 21 Dec 2025 15:04:05 -0500 Subject: [PATCH 221/224] Modify CI cuffing handling --- EXILED/Exiled.CustomItems/API/Features/CustomItem.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs index fa39569e0a..182f47155e 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs @@ -1013,15 +1013,6 @@ private void OnInternalOwnerHandcuffing(HandcuffingEventArgs ev) continue; OnOwnerHandcuffing(new OwnerHandcuffingEventArgs(item, ev)); - - if (!ev.IsAllowed) - continue; - - ev.Target.RemoveItem(item); - - TrackedSerials.Remove(item.Serial); - - Spawn(ev.Target, item, ev.Target); } } From 17e8ded9bf74924dba64f070d7bd1f3bb1cd3e7e Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:04:49 +0100 Subject: [PATCH 222/224] fix: Fix NW that do not give the footprint of Flamingo when attacking Door (#697) fix: FixScp1507DestroyingDoor --- .../Patches/Fixes/FixScp1507DestroyingDoor.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 EXILED/Exiled.Events/Patches/Fixes/FixScp1507DestroyingDoor.cs diff --git a/EXILED/Exiled.Events/Patches/Fixes/FixScp1507DestroyingDoor.cs b/EXILED/Exiled.Events/Patches/Fixes/FixScp1507DestroyingDoor.cs new file mode 100644 index 0000000000..c40037f4b9 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/FixScp1507DestroyingDoor.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using Footprinting; + using HarmonyLib; + using Interactables.Interobjects.DoorUtils; + using PlayerRoles.PlayableScps.Scp1507; + + using static HarmonyLib.AccessTools; + + /// + /// Patches delegate. + /// Fix than NW don't set the footprint argument . + /// + [HarmonyPatch(typeof(Scp1507AttackAbility), nameof(Scp1507AttackAbility.TryAttackDoor))] + internal class FixScp1507DestroyingDoor + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int offset = -1; + int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Initobj) + offset; + + newInstructions.RemoveRange(index, 3); + + newInstructions.InsertRange( + index, + new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(Scp1507AttackAbility), nameof(Scp1507AttackAbility.Owner))), + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(Footprint))[0]), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} From c1c35767dace118128babed5af69ff3ba86b965d Mon Sep 17 00:00:00 2001 From: An4r3w <75725607+An4r3w@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:42:38 +0100 Subject: [PATCH 223/224] feat: added customroles get command (#661) * added customroles get command * added multiple players logic, registered the command, removed PlayerCommandSender check * fixed everything requested * simplify * Make it actually work lol (+ look nicer) --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> --- .../API/Features/CustomRole.cs | 6 + EXILED/Exiled.CustomRoles/Commands/Get.cs | 116 ++++++++++++++++++ EXILED/Exiled.CustomRoles/Commands/Parent.cs | 3 +- .../Exiled.CustomRoles.csproj | 2 +- 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 EXILED/Exiled.CustomRoles/Commands/Get.cs diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index f29e2d525c..afce01ccdf 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -790,6 +790,12 @@ public bool TryAddFriendlyFire(Dictionary ffRules, bool overw return true; } + /// + /// Returns the CustomRole in a human-readable format. + /// + /// A string containing CustomRole-related data. + public override string ToString() => $"{Name} ({Id})"; + /// /// Tries to register this role. /// diff --git a/EXILED/Exiled.CustomRoles/Commands/Get.cs b/EXILED/Exiled.CustomRoles/Commands/Get.cs new file mode 100644 index 0000000000..2bc08b6299 --- /dev/null +++ b/EXILED/Exiled.CustomRoles/Commands/Get.cs @@ -0,0 +1,116 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.CustomRoles.Commands +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Text; + + using CommandSystem; + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using Exiled.CustomRoles.API; + using Exiled.CustomRoles.API.Features; + using Exiled.Permissions.Extensions; + using HarmonyLib; + + /// + /// The command to get specified player(s) current custom roles. + /// + internal sealed class Get : ICommand + { + private Get() + { + } + + /// + /// Gets the command instance. + /// + public static Get Instance { get; } = new(); + + /// + public string Command => "get"; + + /// + public string[] Aliases { get; } = Array.Empty(); + + /// + public string Description => "Gets the specified player(s)' current custom role(s)."; + + /// + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (!sender.CheckPermission("customroles.get")) + { + response = "Permission Denied, required: customroles.get"; + return false; + } + + List players = ListPool.Pool.Get(); + + if (arguments.Count > 0) + { + string identifier = string.Join(" ", arguments); + + switch (identifier) + { + case "*": + case "all": + players.AddRange(Player.List); + break; + default: + players.AddRange(Player.GetProcessedData(arguments)); + break; + } + + if (players.IsEmpty()) + { + if (arguments.Count > 0 || !Player.TryGet(sender, out Player player)) + { + response = $"Player not found: {identifier}"; + return false; + } + + players.Add(player); + } + } + else + { + response = "get "; + return false; + } + + StringBuilder builder = StringBuilderPool.Pool.Get(); + + builder.AppendLine(); + builder.AppendLine("================= Custom Roles ================="); + + foreach (Player target in players) + { + ReadOnlyCollection roles = target.GetCustomRoles(); + if (roles.IsEmpty()) + { + builder.AppendLine($"{target.DisplayNickname.PadRight(30)} | None"); + } + else + { + builder.AppendLine($"{target.DisplayNickname.PadRight(30)} ({target.Id}) | [{string.Join(", ", roles.Select(role => role.ToString()))}]"); + } + } + + builder.AppendLine("================================================"); + + ListPool.Pool.Return(players); + + response = StringBuilderPool.Pool.ToStringReturn(builder); + return true; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.CustomRoles/Commands/Parent.cs b/EXILED/Exiled.CustomRoles/Commands/Parent.cs index f64facabec..fd42b5e497 100644 --- a/EXILED/Exiled.CustomRoles/Commands/Parent.cs +++ b/EXILED/Exiled.CustomRoles/Commands/Parent.cs @@ -41,12 +41,13 @@ public override void LoadGeneratedCommands() RegisterCommand(Give.Instance); RegisterCommand(Info.Instance); RegisterCommand(List.List.Instance); + RegisterCommand(Get.Instance); } /// protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) { - response = "Invalid subcommand! Available: give, info, list"; + response = "Invalid subcommand! Available: give, info, list, get"; return false; } } diff --git a/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj b/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj index 78b2030580..cd1a8b4769 100644 --- a/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj +++ b/EXILED/Exiled.CustomRoles/Exiled.CustomRoles.csproj @@ -49,4 +49,4 @@ if [[ ! -z "$EXILED_DEV_REFERENCES" ]]; then cp "$(OutputPath)$(AssemblyName).dll" "$EXILED_DEV_REFERENCES/Plugins/"; fi - + \ No newline at end of file From 7a317870dc925c6be83ca4decf63d0dc9e665577 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:43:57 +0100 Subject: [PATCH 224/224] bump version v9.12.1 --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 901d77fdcd..7ffb0d1fa9 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.12.0 + 9.12.1 false