From 21f51a2e1006acd495d666ed3e14e9fe26e8fec8 Mon Sep 17 00:00:00 2001 From: Jesus QC <69375249+Jesus-QC@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:51:57 +0200 Subject: [PATCH 1/4] 8.12 (#107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * uwu (#5) * AdminToy.List (#18) * AdminToy.List * Better AdminToy::Get() * Update EXILED/Exiled.API/Features/Toys/AdminToy.cs Co-authored-by: IRacle <79921583+IRacle1@users.noreply.github.com> * TODO * Fix Error * Fix2 --------- Co-authored-by: IRacle <79921583+IRacle1@users.noreply.github.com> * Fix `Jailbird::WearState` (#12) * Jailbird * Fix * Exception * Fix NW moment * Porting EXILED9 RespawnedTeam event. by.VALERA771 (#27) https://github.com/Exiled-Team/EXILED/pull/2386 * Fix not returning null (#22) * Fix not returning null * Apply suggestions from code review Co-authored-by: Jesus QC <69375249+Jesus-QC@users.noreply.github.com> * Little modification --------- Co-authored-by: Jesus QC <69375249+Jesus-QC@users.noreply.github.com> * RecontainedEventArgs more feature (#20) * RecontainedEventArgs more feature * Fix Naming * grammar * Update RecontainedEventArgs.cs --------- Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> * InteractingScp330.cs: Reduction of bloat code from original design. (#30) * Should reduce bloat code that was required years ago. * Should reduce bloat code that was required years ago. * Added back per Yamato's request --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * IScp330Event (#11) Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> * PR than i made (#9) * PR made by me Thanks Ika for the Help on CustomAmmo/CategoryLimit Co-Authored-By: Ika <36999341+IkaOverride@users.noreply.github.com> * Build error * Added support to SCPs for escaping-related events. * spacing * LocalReporting Exiled should be call before NWAPI * Optimising / More documentation on SpawningItem * ISpawnableScp * Use of ComponentsEqualityComparer for Dictionary --------- Co-authored-by: Ika <36999341+IkaOverride@users.noreply.github.com> * [Events] Fix null reference (#15) * fix situation when `ply == null` * lol why --------- Co-authored-by: IRacle * Offline mode support (#19) * Fix Offline-mode breaking everything * Add `offline` authentication type and append `@offline` to UserIds during offline mode * Add offline id support to Player.Get * Comment transpilers --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Fix NW bugs (#32) * Fix Armor Drop from https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/230 * add comments * fix scp173 and adding bug report link to summary class fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/143 * Moved patches and fixed Scp173FirstKillPatch * Add Slowness Fix Avoid values more than 100 for effect slowness to fix https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/378 * skill issue * skill issue (again= --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Useless Event (939 Placed Amnestic Cloud) (#40) * Fix custom role classes giving custom ammo when they are not suppose to (#24) * Fix custom role classes giving custom ammmo when they are not suppose to * change to using EnumUtils * Moved Ammo additions into Inventory call delayed * `Item::Get()` and `Pickup::Get()` (#17) * `Item::::Get()` and `Pickup::Get()` * Revert doc change * Add `Hazard::Get()` * doc fix * `Door::Get()` * AdminToy.Get() * More implementation * WeirdFix * simplify Scp244Spawning patch and AddedComment & NO IL error * Remove Log.info * DroppingItem light modifiication * Moving Item.Get inside the eventargs instead of transpiller * FixNpcNoclip (#34) * FixNpcNoclip * oups * . * virtual / override * Implements more patches for RemovingHandcuffs event and adding RemovedHandcuffs event (#3) * Update labeler.yml * RemovingHandcuffs event * Update UncuffReason.cs * New event * docs * Interacting scp330 compile fix (#43) * My scp built with no issues.. no idea why. * Fixes issue * Harmony suppresses NON harmony errors. --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Trello is no more & more NW Fix & Fix IL Code on dev (#28) * Trello Is Replaced by gitlab * also this one * Fix106RegenerationWithScp244 * Report To NW * . * Scp3114FriendlyFireFix * Fix * yamatotototo * Fix * Fix for building dev + TODO than i just seen * Fix Undid patch * fIX * Fix Client Crash Issue --------- Co-authored-by: IRacle * Additions (#29) * Add a bunch * Fix * Security * Make changes * remove unused usings * Getting inventory * oops * Dev commit * use exiled --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * `[EXILED::API]` Pickup::Category (#46) * Item Category on Pickup * Fixed Build Error * Fix (#50) * `[Exiled::API]` Removing breaking changes (#54) Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * `Scp330` sound fix (#55) Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> * Fix Npc (#58) * Fix breaking change (#61) Fix a breaking change in #9 * Version bump * ๐Ÿ’€๐Ÿ’€๐Ÿ’€ * `SendStaffMessage` fix * Added the SCP-079 Recontaining event (#21) * added the RecontainingEvent with the patch * Fixed constructors * consistency * fixed logic skill issue * Removed "useless" constructor * corrected the typo * fixed some typo * final edit for the final request * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> * `[EXILED::API]` Adding SpawnMice (#47) * Mouse Spawner * Changes to make it public * Optimization * Finally can i spawn rats? * Fix adding throw and changing the error that throws --------- Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> * `[EXILED::API]` Adding ScaleNetworkIdentityObject (#49) * Revert "๐Ÿ’€๐Ÿ’€๐Ÿ’€" This reverts commit 904865e04f86424b77b27023aa7d138ed5dfe220. * `[EXILED::API]` Adding SendFakeSceneLoading (#45) * Added ScenesType and Corrisponding Methods for the Server and Player * Fixing Building Error * Applied the Change request * Changes requested by Yamato made * Fixed Building Errors * Fix build --------- Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> * scp018projectile (#73) * ๐Ÿ˜ฑ๐Ÿ˜Ž๐Ÿ™‚๐Ÿ’€๐Ÿ˜ˆ (#51) * ะ ะฐะทั€ะฐะฑั‹ ะดะฐัƒะฝั‹๐Ÿ˜ฑ๐Ÿ˜Ž๐Ÿ™‚๐Ÿ’€๐Ÿ˜ˆ * new better `SendFakeSyncVar` method * ililililililil * c * `KeycardPickup.Permissions` now is not joke (#70) * `[Exiled::Events]` `Unbanning` and `Unbanned` events (#68) * ๐Ÿฑโ€๐Ÿ‘ค๐Ÿฑโ€๐Ÿ‘ค๐Ÿฑโ€๐Ÿ‘ค * some fixes * fix * โ˜ ๏ธ๐Ÿ’€๐Ÿ’€๐Ÿ’€โ˜ ๏ธ๐Ÿ’€ Co-authored-by: IRacle <79921583+IRacle1@users.noreply.github.com> --------- Co-authored-by: IRacle <79921583+IRacle1@users.noreply.github.com> * `[EXILED::API]` Adding StaminaRegenMultiplier (BodyArmor) (#71) * First push * using removed * Added to the pickup * Forgot to add to Armor (not pickup) * Fix Build error * Simplification * `[EXILED::API]` Adding MoveNetworkIdentityObject (#48) * MoveNetworkObject Added * fix builds * Fix Builds --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * `[EXILED::EVENTS]` Adding PlayingAudioLog 69 PR (#69) * Audio log event * Mistakes * Fix build errors * Update EXILED/Exiled.Events/Patches/Events/Player/PlayingAudioLog.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Update EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Update EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Update EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Update EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * ๐Ÿง ๐Ÿ’ญ๐Ÿ˜”๐Ÿ˜ž๐Ÿ“‰๐Ÿ˜ญ๐Ÿค”โžก๏ธ๐Ÿง‘โ€โš•๏ธ๐Ÿ’ฌ๐Ÿ’Š๐Ÿ“ˆ๐Ÿ˜Œ --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * `RecontainedWhenNoScps` feature (#16) * `[EXILED::EVENTS]` Fixing bug report n.396 (#59) * Fix * Added the Bug report * feat: Added IsDecontaminationEnabled property (#82) * Multiple change (UwU) (#79) Add `Player::AddAmmo(Dictionary ammo)` Add `Player::GrantLoadout()` Add `Player::GrantLoadout(RoleTypeId)` Add `GetEffect()` Add `Scp3114Role::UpdateIdentity()` Fixed `Scp3114Role::DisguiseDuration` not being sync to client * ``[Exiled::CustomRoles]`` ``[Exiled::CustomItems]`` QoL in List commands. (#78) * Ye * I dont need to use ``ci list registered`` only ``ci list`` * You now that ``customroles list abilities`` exist ? no ? yeah its because never say it the command * Its the same for me * Pi pi po po * Im silly @louis1706 uwu --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * DroppingScp330 Event Fix. (#83) * Probably fixes it. * Sigh stylecop --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * ``[Exiled::CustomItems]`` ``[Exiled::API]`` ``[Exiled::CustomRoles]`` Adding news Spawnpoints, Wrapper for Locker and added LockerType (#77) * QoL * Added ``RoomSpawnPoint`` for spawning things in a room with a offset property. * Now Items will be spawned in the MapGenerated event instead of RoundStart avoiding the micro-log for spawning to many pickups in one frame. * YES YAMATO I USE NULLEABLE * Shut * Done * Created a Wrapper for Locker * Created LockerSpawnPoint * I NEED HELP FOR IMPLEMENTING SUPPLYLOCKER I DONT FUCKING KNOW HOW MAKE A TRANSPILER * I hate you Yamato :D * Boop * Why Exiled use MONO.POSIX * And dont use the nuget for it :skull: * Now it will compile in the page * Removing Else if (spawnPoint is RoleSpawnPoint roleSpawnPoint) since its not necessary spawnpoint.Position does the same. * Supressing CS0618 due the obsolet in SpawnLocationType.InsideLocker. * Note: SpawnLocationType.InsideLocker its only used in CustomItem.SpawnAll() so it will be fine to deleted this in futures releases. * Fixing compile action due the warnings * Adding support for Offset Almost forget about this :P * Sorry I cant resist adding more things * Adding support for CustomRoles * Implementing SupplyLocker * I literally copy the generator transpiler * Its works i test it. * Give me my exiled contributor role. * LockerType enum * Code part of the code has been taken from MER (https://github.com/Michal78900/MapEditorReborn/blob/dev/MapEditorReborn/API/Extensions/LockerExtensions.cs) - Credits to Michal, i ask him i can use it, not answer yet but if the say no i will use another way. * SupplyLocker now have a LockerType Property to know what type of Locker is * LockerSpawnPoint can now chose what locker want to use * Mimimi warnings * Re-implementing Locker API * Re-implementing locker api of https://github.com/Exiled-Team/EXILED/pull/2026 * Update EXILED/Exiled.API/Enums/LockerType.cs Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * I dont like the name of ExiledLockers but * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734360930 * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734360419 * Cleaning Chambers List. * Fixing CustomWeapon * Fixing a Bug with custom items with spawning in old SpawnLocationType.InsideLocker * Update Map.cs Removing blank line. * MORE * Added GetRandomSpawnPoint() in Chamber * Added AddItemToSpawn((ItemType itemType, int quantity = 1, bool spawnIfIsOpen = false)) in Chamber * Added IsOpen in chamber. * Fixing obsolet use * Sorry @VALERA771 * Resolve https://github.com/ExMod-Team/EXILED/pull/77#pullrequestreview-2267004377 * Update Exiled.Loader.csproj * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734047353 * Update MapHandler.cs Reduce the delay on spawning items, its not necessary to be to long * Ups Ups --------- Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * `[Exiled::API]` Adding new properties for `FpcRole` (#65) * ๐Ÿ‘‰๐Ÿ‘ˆ * suggestions * no more bc --------- Co-authored-by: Vladislav Popoviฤ * Update programs.yml * Scp0492 change apperance fix (#87) * 0492 appearance change fix. * Yamato is correct, ZombieRole also inherits StandardRoleBase * Stylecop, sigh --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Add plugin website * fix (#95) * Replace with exiled wrapper class (#94) * Fix Role NRE (#66) * `[Exiled::API]` New way to register parent commands (#74) * uwu * optimising * Allow `ID_Dedicated`/`null` for `Npc.UserId` Parameter (#75) * Npc * Fix Build Error * Update EXILED/Exiled.API/Features/Npc.cs Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * Fix for the PR * Fix Pr * Fixing a small issue if the player is choosing something not ID_DEDICATED * Fix * forgot --------- Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> * Update Exiled.Loader.csproj (#80) * It gives me anxiety to use something that can be a nuget. Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * [Events] Fix crash (#96) * fix * oh, already made in #95 * Random fixes (#103) * fixes * also * Adding Turning On All Lights (#105) * `[EXILED::Events]` Introduce `hub install` command (#101) * feat: introduce plugin installation command * fix: fix build errors wtf was this actually * version bump --------- Co-authored-by: VALERA771 <72030575+VALERA771@users.noreply.github.com> Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: IRacle <79921583+IRacle1@users.noreply.github.com> Co-authored-by: ๆฐธๅฎ‰404 <101850798+YongAn404@users.noreply.github.com> Co-authored-by: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> Co-authored-by: X <24619207+Undid-Iridium@users.noreply.github.com> Co-authored-by: Ika <36999341+IkaOverride@users.noreply.github.com> Co-authored-by: IRacle Co-authored-by: x3rt Co-authored-by: sky <99112969+skyyt15@users.noreply.github.com> Co-authored-by: TtroubleTT <121741230+TtroubleTT@users.noreply.github.com> Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> Co-authored-by: ZeroTwo <63092138+NotZer0Two@users.noreply.github.com> Co-authored-by: FoxWorn3365 <61429263+FoxWorn3365@users.noreply.github.com> Co-authored-by: DrBright <125764730+alexomur@users.noreply.github.com> Co-authored-by: SrLicht Co-authored-by: Vladislav Popoviฤ Co-authored-by: Alex Rouse <123724383+ALEXWARELLC@users.noreply.github.com> Co-authored-by: Joseph <108951296+Josephfallen@users.noreply.github.com> Co-authored-by: Panikorovskii Vladislav <68610520+bladuk@users.noreply.github.com> --- EXILED/EXILED.props | 2 +- EXILED/Exiled.API/Enums/LockerType.cs | 50 ++++ EXILED/Exiled.API/Enums/ScenesType.cs | 47 ++++ EXILED/Exiled.API/Enums/SpawnLocationType.cs | 4 +- .../Exiled.API/Extensions/LockerExtensions.cs | 43 ++++ .../Exiled.API/Extensions/MirrorExtensions.cs | 156 +++++++++++- EXILED/Exiled.API/Features/Items/Armor.cs | 8 +- EXILED/Exiled.API/Features/Items/Firearm.cs | 14 +- EXILED/Exiled.API/Features/Items/Usable.cs | 2 - EXILED/Exiled.API/Features/Lockers/Chamber.cs | 232 ++++++++++++++++++ EXILED/Exiled.API/Features/Lockers/Locker.cs | 230 +++++++++++++++++ EXILED/Exiled.API/Features/Map.cs | 66 ++++- EXILED/Exiled.API/Features/Npc.cs | 66 +++-- .../Features/Pickups/BodyArmorPickup.cs | 7 + EXILED/Exiled.API/Features/Pickups/Pickup.cs | 1 + .../Pickups/Projectiles/Scp018Projectile.cs | 20 +- EXILED/Exiled.API/Features/Player.cs | 62 ++++- EXILED/Exiled.API/Features/Plugin.cs | 71 +++++- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 33 ++- .../Exiled.API/Features/Roles/IVoiceRole.cs | 22 ++ EXILED/Exiled.API/Features/Roles/NoneRole.cs | 6 +- EXILED/Exiled.API/Features/Roles/Role.cs | 7 +- .../Exiled.API/Features/Roles/Scp0492Role.cs | 2 +- .../Exiled.API/Features/Roles/Scp049Role.cs | 4 +- .../Exiled.API/Features/Roles/Scp079Role.cs | 6 +- .../Exiled.API/Features/Roles/Scp3114Role.cs | 38 ++- .../Features/Roles/SpectatorRole.cs | 7 +- EXILED/Exiled.API/Features/Server.cs | 2 + .../Features/Spawn/LockerSpawnPoint.cs | 72 ++++++ .../Features/Spawn/RoomSpawnPoint.cs | 56 +++++ .../Features/Spawn/SpawnProperties.cs | 12 +- .../API/Features/CustomItem.cs | 26 +- EXILED/Exiled.CustomItems/Commands/Info.cs | 11 +- .../Exiled.CustomItems/Commands/List/List.cs | 9 +- EXILED/Exiled.CustomItems/CustomItems.cs | 8 +- .../Exiled.CustomItems/Events/MapHandler.cs | 28 +++ .../Exiled.CustomItems/Events/RoundHandler.cs | 24 -- .../API/Features/CustomRole.cs | 10 + .../Exiled.CustomRoles/Commands/List/List.cs | 9 +- .../Exiled.Events/Commands/Config/EConfig.cs | 10 - EXILED/Exiled.Events/Commands/Config/Merge.cs | 1 + EXILED/Exiled.Events/Commands/Config/Split.cs | 1 + 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 | 116 +++++++++ .../Commands/PluginManager/Disable.cs | 1 + .../Commands/PluginManager/Enable.cs | 1 + .../Commands/PluginManager/Patches.cs | 1 + .../Commands/PluginManager/PluginManager.cs | 12 - .../Commands/PluginManager/Show.cs | 1 + EXILED/Exiled.Events/Commands/Reload/All.cs | 1 + .../Exiled.Events/Commands/Reload/Configs.cs | 1 + .../Exiled.Events/Commands/Reload/GamePlay.cs | 1 + .../Commands/Reload/Permissions.cs | 1 + .../Exiled.Events/Commands/Reload/Plugins.cs | 1 + .../Exiled.Events/Commands/Reload/Reload.cs | 15 -- .../Commands/Reload/RemoteAdmin.cs | 1 + .../Commands/Reload/Translations.cs | 1 + EXILED/Exiled.Events/Config.cs | 8 +- .../Item/KeycardInteractingEventArgs.cs | 7 +- .../EventArgs/Map/FillingLockerEventArgs.cs | 20 +- .../Player/InteractingLockerEventArgs.cs | 39 +-- .../Player/KillingPlayerEventArgs.cs | 4 +- .../Player/PlayingAudioLogEventArgs.cs | 44 ++++ .../EventArgs/Scp079/RecontainingEventArgs.cs | 42 ++++ .../EventArgs/Server/UnbannedEventArgs.cs | 36 +++ .../EventArgs/Server/UnbanningEventArgs.cs | 43 ++++ EXILED/Exiled.Events/Exiled.Events.csproj | 1 + .../Handlers/Internal/MapGenerated.cs | 3 +- EXILED/Exiled.Events/Handlers/Player.cs | 16 +- EXILED/Exiled.Events/Handlers/Scp079.cs | 11 + EXILED/Exiled.Events/Handlers/Server.cs | 22 ++ .../Patches/Events/Item/KeycardInteracting.cs | 62 +++-- .../Events/Player/ActivatingWorkstation.cs | 2 +- .../Events/Player/ChangingDangerState.cs | 2 +- .../Events/Player/ChangingRoleAndSpawned.cs | 33 +-- .../Events/Player/InteractingLocker.cs | 6 - .../Patches/Events/Player/PlayingAudioLog.cs | 68 +++++ .../Patches/Events/Player/VoiceChatting.cs | 5 +- .../Patches/Events/Scp079/Recontaining.cs | 67 +++++ .../Patches/Events/Scp096/AddingTarget.cs | 2 +- .../Patches/Events/Scp096/CalmingDown.cs | 2 +- .../Patches/Events/Scp173/BlinkingRequest.cs | 2 +- .../Patches/Events/Scp330/DroppingCandy.cs | 15 +- .../Patches/Events/Server/AddingUnitName.cs | 2 +- .../Patches/Events/Server/Unban.cs | 91 +++++++ .../Exiled.Events/Patches/Fixes/KillPlayer.cs | 2 + .../Patches/Fixes/NWFixDetonationTimer.cs | 32 +++ .../Exiled.Events/Patches/Generic/DoorList.cs | 42 +++- .../Patches/Generic/LockerList.cs | 12 +- .../Patches/Generic/ParseVisionInformation.cs | 2 +- .../Patches/Generic/Scp079Recontain.cs | 54 ++++ .../Patches/Generic/StaminaRegenArmor.cs | 30 +++ EXILED/Exiled.Loader/Exiled.Loader.csproj | 1 + EXILED/Exiled.Loader/LinuxPermission.cs | 4 +- 96 files changed, 2319 insertions(+), 304 deletions(-) create mode 100644 EXILED/Exiled.API/Enums/LockerType.cs create mode 100644 EXILED/Exiled.API/Enums/ScenesType.cs create mode 100644 EXILED/Exiled.API/Extensions/LockerExtensions.cs create mode 100644 EXILED/Exiled.API/Features/Lockers/Chamber.cs create mode 100644 EXILED/Exiled.API/Features/Lockers/Locker.cs create mode 100644 EXILED/Exiled.API/Features/Roles/IVoiceRole.cs create mode 100644 EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs create mode 100644 EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs create mode 100644 EXILED/Exiled.CustomItems/Events/MapHandler.cs delete mode 100644 EXILED/Exiled.CustomItems/Events/RoundHandler.cs create mode 100644 EXILED/Exiled.Events/Commands/Hub/Hub.cs create mode 100644 EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs create mode 100644 EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs create mode 100644 EXILED/Exiled.Events/Commands/Hub/Install.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Scp079/RecontainingEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs create mode 100644 EXILED/Exiled.Events/EventArgs/Server/UnbanningEventArgs.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Player/PlayingAudioLog.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Scp079/Recontaining.cs create mode 100644 EXILED/Exiled.Events/Patches/Events/Server/Unban.cs create mode 100644 EXILED/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/Scp079Recontain.cs create mode 100644 EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 2efd458a56..0f91e3906f 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 8.12.0-rc.3 + 8.12.0 false diff --git a/EXILED/Exiled.API/Enums/LockerType.cs b/EXILED/Exiled.API/Enums/LockerType.cs new file mode 100644 index 0000000000..c44037a3a7 --- /dev/null +++ b/EXILED/Exiled.API/Enums/LockerType.cs @@ -0,0 +1,50 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// Unique identifier for different types of s. + /// + public enum LockerType + { + /// + /// The pedestal used by SCP items. + /// + Pedestal, + + /// + /// Large weapon locker. + /// + LargeGun, + + /// + /// Locker for rifles, known as a rifle rack. + /// + RifleRack, + + /// + /// Miscellaneous locker for various items. + /// + Misc, + + /// + /// Locker that contains medkits. + /// + Medkit, + + /// + /// Locker that contains adrenaline. + /// + Adrenaline, + + /// + /// Unknow type of locker. + /// + Unknow, + } +} diff --git a/EXILED/Exiled.API/Enums/ScenesType.cs b/EXILED/Exiled.API/Enums/ScenesType.cs new file mode 100644 index 0000000000..2a7898ca6e --- /dev/null +++ b/EXILED/Exiled.API/Enums/ScenesType.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// Unique identifier for the different types of Scenes the client and server can load. + /// + public enum ScenesType + { + /// + /// The facility itself. + /// + Facility, + + /// + /// The current main menu. + /// ! Will cause crash when trying joining servers ! + /// + NewMainMenu, + + /// + /// The old main menu. + /// + MainMenuRemastered, + + /// + /// The old server list. + /// + FastMenu, + + /// + /// The loading Screen. + /// ! Will cause crash when trying joining servers ! + /// + PreLoader, + + /// + /// A black menu before loading the . + /// + Loader, + } +} diff --git a/EXILED/Exiled.API/Enums/SpawnLocationType.cs b/EXILED/Exiled.API/Enums/SpawnLocationType.cs index 852e07f432..63568ba3ba 100644 --- a/EXILED/Exiled.API/Enums/SpawnLocationType.cs +++ b/EXILED/Exiled.API/Enums/SpawnLocationType.cs @@ -4,9 +4,10 @@ // Licensed under the CC BY-SA 3.0 license. // // ----------------------------------------------------------------------- - namespace Exiled.API.Enums { + using System; + /// /// All of the valid spawn location types. /// @@ -150,6 +151,7 @@ public enum SpawnLocationType /// /// Inside a random locker on the map. /// + [Obsolete("Use LockerSpawnPoint instead")] InsideLocker, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/LockerExtensions.cs b/EXILED/Exiled.API/Extensions/LockerExtensions.cs new file mode 100644 index 0000000000..7304cbd4e6 --- /dev/null +++ b/EXILED/Exiled.API/Extensions/LockerExtensions.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + + using Exiled.API.Enums; + using MapGeneration.Distributors; + + /// + /// A set of extensions for . + /// + public static class LockerExtensions + { + /// + /// Gets the from the given object. + /// + /// The to check. + /// The corresponding . + public static LockerType GetLockerType(this Locker locker) => locker.name.GetLockerTypeByName(); + + /// + /// Gets the by name. + /// + /// The name to check. + /// The corresponding . + public static LockerType GetLockerTypeByName(this string name) => name.Replace("(Clone)", string.Empty) switch + { + "Scp500PedestalStructure Variant" => LockerType.Pedestal, + "LargeGunLockerStructure" => LockerType.LargeGun, + "RifleRackStructure" => LockerType.RifleRack, + "MiscLocker" => LockerType.Misc, + "RegularMedkitStructure" => LockerType.Medkit, + "AdrenalineMedkitStructure" => LockerType.Adrenaline, + _ => LockerType.Unknow, + }; + } +} diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index e85b54ed6b..568eabf511 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 Exiled.API.Enums; using Features; using Features.Pools; @@ -270,6 +271,15 @@ public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumer writer.WriteByte(unitId); } + if (roleBase is ZombieRole) + { + if (player.Role.Base is not ZombieRole) + isRisky = true; + + writer.WriteUShort((ushort)Mathf.Clamp(Mathf.CeilToInt(player.MaxHealth), ushort.MinValue, ushort.MaxValue)); + writer.WriteBool(true); + } + if (roleBase is FpcStandardRoleBase fpc) { if (player.Role.Base is not FpcStandardRoleBase playerfpc) @@ -283,14 +293,6 @@ public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumer writer.WriteUShort(value); } - if (roleBase is ZombieRole) - { - if (player.Role.Base is not ZombieRole) - isRisky = true; - - writer.WriteUShort((ushort)Mathf.Clamp(Mathf.CeilToInt(player.MaxHealth), ushort.MinValue, ushort.MaxValue)); - } - foreach (Player target in playersToAffect) { if (target != player || !isRisky) @@ -355,6 +357,111 @@ public static void MessageTranslated(this Player player, string words, string tr } } + /// + /// Moves object for the player. + /// + /// Target to send. + /// The to move. + /// The position to change. + public static void MoveNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 pos) + { + identity.gameObject.transform.position = pos; + ObjectDestroyMessage objectDestroyMessage = new() + { + netId = identity.netId, + }; + + player.Connection.Send(objectDestroyMessage, 0); + SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection }); + } + + /// + /// Sends to the player a Fake Change Scene. + /// + /// The player to send the Scene. + /// The new Scene the client will load. + public static void SendFakeSceneLoading(this Player player, ScenesType newSceneName) + { + SceneMessage message = new() + { + sceneName = newSceneName.ToString(), + }; + + player.Connection.Send(message); + } + + /// + /// Emulation of the method SCP:SL uses to change scene. + /// + /// The new Scene the client will load. + public static void ChangeSceneToAllClients(ScenesType scene) + { + SceneMessage message = new() + { + sceneName = scene.ToString(), + }; + + NetworkServer.SendToAll(message); + } + + /// + /// Scales an object for the specified player. + /// + /// Target to send. + /// The to scale. + /// The scale the object needs to be set to. + public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 scale) + { + identity.gameObject.transform.localScale = scale; + ObjectDestroyMessage objectDestroyMessage = new() + { + netId = identity.netId, + }; + + player.Connection.Send(objectDestroyMessage, 0); + SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection }); + } + + /// + /// Moves object for all the players. + /// + /// The to move. + /// The position to change. + public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vector3 pos) + { + identity.gameObject.transform.position = pos; + ObjectDestroyMessage objectDestroyMessage = new() + { + netId = identity.netId, + }; + + foreach (Player ply in Player.List) + { + ply.Connection.Send(objectDestroyMessage, 0); + SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, ply.Connection }); + } + } + + /// + /// Scales an object for all players. + /// + /// The to scale. + /// The scale the object needs to be set to. + public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vector3 scale) + { + identity.gameObject.transform.localScale = scale; + ObjectDestroyMessage objectDestroyMessage = new() + { + netId = identity.netId, + }; + + foreach (Player ply in Player.List) + { + ply.Connection.Send(objectDestroyMessage, 0); + SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, ply.Connection }); + } + } + /// /// Send fake values to client's . /// @@ -363,6 +470,7 @@ public static void MessageTranslated(this Player player, string words, string tr /// 's type. /// Property name starting with Network. /// Value of send to target. + [Obsolete("Use overload with type-template instead.")] public static void SendFakeSyncVar(this Player target, NetworkIdentity behaviorOwner, Type targetType, string propertyName, object value) { if (!target.IsConnected) @@ -386,6 +494,38 @@ void CustomSyncVarGenerator(NetworkWriter targetWriter) } } + /// + /// Send fake values to client's . + /// + /// Target SyncVar property type. + /// Target to send. + /// of object that owns . + /// 's type. + /// Property name starting with Network. + /// Value of send to target. + public static void SendFakeSyncVar(this Player target, NetworkIdentity behaviorOwner, Type targetType, string propertyName, T value) + { + if (!target.IsConnected) + return; + + NetworkWriterPooled writer = NetworkWriterPool.Get(); + NetworkWriterPooled writer2 = NetworkWriterPool.Get(); + MakeCustomSyncWriter(behaviorOwner, targetType, null, CustomSyncVarGenerator, writer, writer2); + target.Connection.Send(new EntityStateMessage + { + netId = behaviorOwner.netId, + payload = writer.ToArraySegment(), + }); + + NetworkWriterPool.Return(writer); + NetworkWriterPool.Return(writer2); + void CustomSyncVarGenerator(NetworkWriter targetWriter) + { + targetWriter.WriteULong(SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"]); + WriterExtensions[typeof(T)]?.Invoke(null, new object[2] { targetWriter, value }); + } + } + /// /// Force resync to client's . /// diff --git a/EXILED/Exiled.API/Features/Items/Armor.cs b/EXILED/Exiled.API/Features/Items/Armor.cs index 8f176e676f..1a46bc3606 100644 --- a/EXILED/Exiled.API/Features/Items/Armor.cs +++ b/EXILED/Exiled.API/Features/Items/Armor.cs @@ -15,7 +15,6 @@ namespace Exiled.API.Features.Items using Exiled.API.Interfaces; using InventorySystem.Items.Armor; - using PlayerRoles; using Structs; @@ -110,6 +109,11 @@ public float StaminaUseMultiplier set => Base._staminaUseMultiplier = value; } + /// + /// Gets or sets the stamina regen multiplier. + /// + public float StaminaRegenMultiplier { get; set; } = 1f; + /// /// Gets or sets how much the users movement speed should be affected when wearing this armor. (higher values = slower movement). /// @@ -153,6 +157,7 @@ public IEnumerable CategoryLimits StaminaUseMultiplier = StaminaUseMultiplier, RemoveExcessOnDrop = RemoveExcessOnDrop, CategoryLimits = CategoryLimits, + StaminaRegenMultiplier = StaminaRegenMultiplier, AmmoLimits = AmmoLimits, VestEfficacy = VestEfficacy, HelmetEfficacy = HelmetEfficacy, @@ -168,6 +173,7 @@ internal override void ReadPickupInfo(Pickup pickup) VestEfficacy = armorPickup.VestEfficacy; RemoveExcessOnDrop = armorPickup.RemoveExcessOnDrop; StaminaUseMultiplier = armorPickup.StaminaUseMultiplier; + StaminaRegenMultiplier = armorPickup.StaminaRegenMultiplier; AmmoLimits = armorPickup.AmmoLimits; CategoryLimits = armorPickup.CategoryLimits; } diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index e05a9180f6..f9b4b2cece 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -633,18 +633,10 @@ internal override void ChangeOwner(Player oldOwner, Player newOwner) { Base.Owner = newOwner.ReferenceHub; - Base.HitregModule = Base switch + if (Base.HitregModule is StandardHitregBase hitReg) { - AutomaticFirearm automaticFirearm => - new SingleBulletHitreg(automaticFirearm, automaticFirearm.Owner, automaticFirearm._recoilPattern), - Shotgun shotgun => - new BuckshotHitreg(shotgun, shotgun.Owner, shotgun.GetBuckshotPattern), - ParticleDisruptor particleDisruptor => - new DisruptorHitreg(particleDisruptor, particleDisruptor.Owner, particleDisruptor._explosionSettings), - Revolver revolver => - new SingleBulletHitreg(revolver, revolver.Owner), - _ => throw new NotImplementedException("Should never happend"), - }; + hitReg.Hub = Base.Owner; + } Base._sendStatusNextFrame = true; Base._footprintValid = false; diff --git a/EXILED/Exiled.API/Features/Items/Usable.cs b/EXILED/Exiled.API/Features/Items/Usable.cs index c6c7f1b334..aba803d38d 100644 --- a/EXILED/Exiled.API/Features/Items/Usable.cs +++ b/EXILED/Exiled.API/Features/Items/Usable.cs @@ -115,8 +115,6 @@ public override Pickup CreatePickup(Vector3 position, Quaternion rotation = defa ItemPickupBase ipb = InventoryExtensions.ServerCreatePickup(Base, info, position, rotation); - Base.OnRemoved(ipb); - Pickup pickup = Pickup.Get(ipb); if (spawn) diff --git a/EXILED/Exiled.API/Features/Lockers/Chamber.cs b/EXILED/Exiled.API/Features/Lockers/Chamber.cs new file mode 100644 index 0000000000..8209d39a5f --- /dev/null +++ b/EXILED/Exiled.API/Features/Lockers/Chamber.cs @@ -0,0 +1,232 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Lockers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features.Pickups; + using Exiled.API.Interfaces; + using MapGeneration.Distributors; + using UnityEngine; + + /// + /// A wrapper for . + /// + public class Chamber : IWrapper, IWorldSpace + { + /// + /// with and . + /// + internal static readonly Dictionary Chambers = new(); + + /// + /// Initializes a new instance of the class. + /// + /// instance. + /// where this chamber is located. + public Chamber(LockerChamber chamber, Locker locker) + { + Base = chamber; + Locker = locker; + Id = (byte)Array.IndexOf(locker.Base.Chambers, chamber); + + Chambers.Add(chamber, this); + } + + /// + /// Gets a of which contains all the instances. + /// + public static IReadOnlyCollection List => Chambers.Values; + + /// + public LockerChamber Base { get; } + + /// + /// Gets the where this chamber is located at. + /// + public Locker Locker { get; } + + /// + public Vector3 Position => Base.transform.position; + + /// + public Quaternion Rotation => Base.transform.rotation; + + /// + /// Gets or sets all pickups that should be spawned when the door is initially opened. + /// + public IEnumerable ToBeSpawned + { + get => Base._toBeSpawned.Select(Pickup.Get); + set + { + Base._toBeSpawned.Clear(); + + foreach (Pickup pickup in value) + Base._toBeSpawned.Add(pickup.Base); + } + } + + /// + /// Gets or sets all spawn points. + /// + /// + /// Used if is set to . + /// + public IEnumerable Spawnpoints + { + get => Base._spawnpoints; + set => Base._spawnpoints = value.ToArray(); + } + + /// + /// Gets or sets all the acceptable items which can be spawned in this chamber. + /// + public IEnumerable AcceptableTypes + { + get => Base.AcceptableItems; + set => Base.AcceptableItems = value.ToArray(); + } + + /// + /// Gets or sets required permissions to open this chamber. + /// + public KeycardPermissions RequiredPermissions + { + get => (KeycardPermissions)Base.RequiredPermissions; + set => Base.RequiredPermissions = (Interactables.Interobjects.DoorUtils.KeycardPermissions)value; + } + + /// + /// Gets or sets a value indicating whether multiple spawn points should be used. + /// + /// + /// If , will be used over . + /// + public bool UseMultipleSpawnpoints + { + get => Base._useMultipleSpawnpoints; + set => Base._useMultipleSpawnpoints = value; + } + + /// + /// Gets or sets a spawn point for the items in the chamber. + /// + /// + /// Used if is set to . + /// + public Transform Spawnpoint + { + get => Base._spawnpoint; + set => Base._spawnpoint = value; + } + + /// + /// Gets or sets a value indicating whether or not items should be spawned as soon as they one chamber is opened. + /// + public bool InitiallySpawn + { + get => Base._spawnOnFirstChamberOpening; + set => Base._spawnOnFirstChamberOpening = value; + } + + /// + /// Gets or sets the amount of time before a player can interact with the chamber again. + /// + public float Cooldown + { + get => Base._targetCooldown; + set => Base._targetCooldown = value; + } + + /// + /// Gets a value indicating whether the chamber is currently open. + /// + public bool IsOpen => Base.IsOpen; + + /// + /// Gets the id of this chamber in . + /// + public byte Id { get; } + + /// + /// Gets the of current cooldown. + /// + /// Used in check. + public Stopwatch CurrentCooldown => Base._stopwatch; + + /// + /// Gets a value indicating whether the chamber is interactable. + /// + public bool CanInteract => Base.CanInteract; + + /// + /// Spawns a specified item from . + /// + /// from . + /// Amount of items that should be spawned. + public void SpawnItem(ItemType type, int amount) => Base.SpawnItem(type, amount); + + /// + /// Adds an item of the specified type to the chamber's spawn list. + /// If the chamber is open and is set to , + /// the item is spawned immediately at a random spawn point within the chamber. + /// + /// The type of item to add to the spawn list. + /// The number of items to add. Defaults to 1. + /// + /// If and the chamber is open, the item is immediately spawned at a random spawn point. + /// Otherwise, the item is added to the spawn list and will spawn when the chamber is opened. + /// + public void AddItemToSpawn(ItemType itemType, int quantity = 1, bool spawnIfIsOpen = false) + { + for (int i = 0; i < quantity; i++) + { + Pickup pickup = Pickup.Create(itemType); + + if (spawnIfIsOpen && IsOpen) + { + pickup.Position = GetRandomSpawnPoint(); + pickup.Spawn(); + continue; + } + + Base._toBeSpawned.Add(pickup.Base); + } + } + + /// + /// Gets a random spawn point within the chamber. + /// If multiple spawn points are available and is , + /// a random spawn point is selected from the available points. + /// Otherwise, the default spawn point is used. + /// + /// A representing the position of the selected spawn point. + public Vector3 GetRandomSpawnPoint() + { + if (UseMultipleSpawnpoints && Spawnpoints.Any()) + { + return Spawnpoints.GetRandomValue().position; + } + + return Spawnpoint.position; + } + + /// + /// Gets the chamber by its . + /// + /// . + /// . + 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()); + } +} diff --git a/EXILED/Exiled.API/Features/Lockers/Locker.cs b/EXILED/Exiled.API/Features/Lockers/Locker.cs new file mode 100644 index 0000000000..73108359bb --- /dev/null +++ b/EXILED/Exiled.API/Features/Lockers/Locker.cs @@ -0,0 +1,230 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Lockers +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features; + using Exiled.API.Features.Pickups; + using Exiled.API.Interfaces; + + using InventorySystem.Items.Pickups; + using MapGeneration.Distributors; + + using Mirror; + using UnityEngine; + + using BaseLocker = MapGeneration.Distributors.Locker; +#nullable enable + /// + /// The in-game Locker. + /// + public class Locker : IWrapper, IWorldSpace + { + /// + /// A containing all known s and their corresponding . + /// + internal static readonly Dictionary BaseToExiledLockers = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The encapsulated . + public Locker(BaseLocker locker) + { + Base = locker; + BaseToExiledLockers.Add(locker, this); + + Chambers = locker.Chambers.Select(x => new Chamber(x, this)).ToList(); + Type = locker.GetLockerType(); + } + + /// + /// Gets a of which contains all the instances. + /// + public static IReadOnlyCollection List => BaseToExiledLockers.Values; + + /// + public BaseLocker Base { get; } + + /// + /// Gets the of the . + /// + public LockerType Type { get; } + + /// + /// Gets the . + /// + public Transform Transform => Base.transform; + + /// + public Vector3 Position => Base.transform.position; + + /// + public Quaternion Rotation => Base.transform.rotation; + + /// + /// Gets the in which the is located. + /// + public Room? Room => Room.Get(Position); + + /// + /// Gets the in which the locker is located. + /// + public ZoneType Zone => Room?.Zone ?? ZoneType.Unspecified; + + /// + /// Gets the all in this locker. + /// + public IReadOnlyCollection Chambers { get; } + + /// + /// Gets or sets an id for manipulating opened chambers. + /// + public ushort OpenedChambers + { + get => Base.OpenedChambers; + set => Base.NetworkOpenedChambers = value; + } + + /// + /// Gets a random position from one of the . + /// + public Vector3 RandomChamberPosition + { + get + { + Chamber randomChamber = Chambers.GetRandomValue(); + + // Determine if the chamber uses multiple spawn points and has at least one available spawn point. + if (randomChamber.UseMultipleSpawnpoints && randomChamber.Spawnpoints.Count() > 0) + { + // Return the position of a random spawn point within the chamber. + return randomChamber.Spawnpoints.GetRandomValue().position; + } + + // Return the position of the main spawn point for the chamber. + return randomChamber.Spawnpoint.position; + } + } + + /// + /// Gets the belonging to the , if any. + /// + /// The to get. + /// A or if not found. + public static Locker? Get(BaseLocker locker) => locker == null ? null : + BaseToExiledLockers.TryGetValue(locker, out Locker supply) ? supply : new Locker(locker); + + /// + /// Gets a of given the specified . + /// + /// The to search for. + /// The with the given or if not found. + public static IEnumerable Get(ZoneType zoneType) => Get(room => room.Zone.HasFlag(zoneType)); + + /// + /// Gets a of filtered based on a predicate. + /// + /// The condition to satify. + /// A of which contains elements that satify the condition. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + + /// + /// Gets a random based on the specified filters. + /// + /// The to filter by. If unspecified, all zones are considered. + /// The to filter by. If unspecified, all locker types are considered. + /// A random object, or if no matching locker is found. + public static Locker? Random(ZoneType zone = ZoneType.Unspecified, LockerType lockerType = LockerType.Unknow) + { + IEnumerable filteredLockers = List; + + if (lockerType != LockerType.Unknow) + filteredLockers = filteredLockers.Where(l => l.Type == lockerType); + + if (zone != ZoneType.Unspecified) + filteredLockers = filteredLockers.Where(l => l.Zone == zone); + + return filteredLockers.GetRandomValue(); + } + + /// + /// Adds an item to a randomly selected locker chamber. + /// + /// The to be added to the locker chamber. + public void AddItem(Pickup item) + { + // Select a random chamber from the available locker chambers. + Chamber chamber = Chambers.GetRandomValue(); + + // Determine the parent transform where the item will be placed. + Transform parentTransform = chamber.UseMultipleSpawnpoints && chamber.Spawnpoints.Count() > 0 + ? chamber.Spawnpoints.GetRandomValue() + : chamber.Spawnpoint; + + // If the chamber is open, immediately set the item's parent and spawn it. + if (chamber.Base.IsOpen) + { + item.Transform.SetParent(parentTransform); + item.Spawn(); + } + else + { + // If the item is already spawned on the network, unspawn it before proceeding. + if (NetworkServer.spawned.ContainsKey(item.Base.netId)) + NetworkServer.UnSpawn(item.GameObject); + + // Set the item's parent transform. + item.Transform.SetParent(parentTransform); + + // Lock the item in place. + item.IsLocked = true; + + // Notify any pickup distributor triggers. + (item.Base as IPickupDistributorTrigger)?.OnDistributed(); + + // If the item has a Rigidbody component, make it kinematic and reset its position and rotation. + if (item.Rigidbody != null) + { + item.Rigidbody.isKinematic = true; + item.Rigidbody.transform.localPosition = Vector3.zero; + item.Rigidbody.transform.localRotation = Quaternion.identity; + + // Add the Rigidbody to the list of bodies to be unfrozen later. + SpawnablesDistributorBase.BodiesToUnfreeze.Add(item.Rigidbody); + } + + // If the chamber is configured to spawn items on the first opening, add the item to the list of items to be spawned. + // Otherwise, spawn the item immediately. + if (chamber.InitiallySpawn) + chamber.Base._toBeSpawned.Add(item.Base); + else + ItemDistributor.SpawnPickup(item.Base); + } + } + + /// + /// Spawns an item of the specified to the locker by creating a new . + /// + /// The type of item to be added. + public void AddItem(ItemType type) => AddItem(Pickup.Create(type)); + + /// + /// Clears the cached lockers in the dictionary. + /// + internal static void ClearCache() + { + BaseToExiledLockers.Clear(); + Chamber.Chambers.Clear(); + } + } +} diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 3f1f6fc1a0..d0b31f3919 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -16,6 +16,7 @@ namespace Exiled.API.Features using Enums; using Exiled.API.Extensions; using Exiled.API.Features.Hazards; + using Exiled.API.Features.Lockers; using Exiled.API.Features.Pickups; using Exiled.API.Features.Toys; using global::Hazards; @@ -42,11 +43,6 @@ namespace Exiled.API.Features /// public static class Map { - /// - /// A list of s on the map. - /// - internal static readonly List LockersValue = new(35); - /// /// A list of s on the map. /// @@ -54,6 +50,8 @@ public static class Map private static AmbientSoundPlayer ambientSoundPlayer; + private static SqueakSpawner squeakSpawner; + /// /// Gets the tantrum prefab. /// @@ -82,9 +80,13 @@ DecontaminationController.Singleton.NetworkDecontaminationOverride is Decontamin public static ReadOnlyCollection PocketDimensionTeleports { get; } = TeleportsValue.AsReadOnly(); /// - /// Gets all objects. + /// Gets all objects in the current map. /// - public static ReadOnlyCollection Lockers { get; } = LockersValue.AsReadOnly(); + /// + /// This property is obsolete. Use instead to retrieve a collection of all instances. + /// + [Obsolete("Use Locker.List instead.")] + public static ReadOnlyCollection Lockers { get; } = Features.Lockers.Locker.BaseToExiledLockers.Keys.ToList().AsReadOnly(); /// /// Gets all objects. @@ -104,11 +106,28 @@ public static int Seed } } + /// + /// Gets or sets a value indicating whether decontamination is enabled. + /// + public static bool IsDecontaminationEnabled + { + get => DecontaminationController.Singleton.NetworkDecontaminationOverride == DecontaminationController.DecontaminationStatus.None; + set => + DecontaminationController.Singleton.NetworkDecontaminationOverride = value + ? DecontaminationController.DecontaminationStatus.None + : DecontaminationController.DecontaminationStatus.Disabled; + } + /// /// Gets the . /// public static AmbientSoundPlayer AmbientSoundPlayer => ambientSoundPlayer ??= ReferenceHub.HostHub.GetComponent(); + /// + /// Gets the . + /// + public static SqueakSpawner SqueakSpawner => squeakSpawner ??= Object.FindObjectOfType(); + /// /// Broadcasts a message to all players. /// @@ -152,10 +171,16 @@ public static void ShowHint(string message, float duration = 3f) public static void ClearBroadcasts() => Server.Broadcast.RpcClearElements(); /// - /// Starts the light containment zone decontamination process. + /// Forces the light containment zone decontamination process. /// public static void StartDecontamination() => DecontaminationController.Singleton.ForceDecontamination(); + /// + /// Turns on all lights in the facility. + /// + /// The s to affect. + public static void TurnOnAllLights(IEnumerable zoneTypes) => TurnOffAllLights(0, zoneTypes); + /// /// Turns off all lights in the facility. /// @@ -205,10 +230,14 @@ public static void ResetLightsColor() } /// - /// Gets a random . + /// Gets a random object from the current map. /// - /// object. - public static Locker GetRandomLocker() => Lockers.GetRandomValue(); + /// + /// This method is obsolete. Use instead to get a random instance. + /// + /// A randomly selected object. + [Obsolete("Use Locker.Random() instead.")] + public static MapGeneration.Distributors.Locker GetRandomLocker() => Lockers.GetRandomValue(); /// /// Gets a random . @@ -362,6 +391,19 @@ public static void PlayGunSound(Vector3 position, ItemType firearmType, byte max msg.SendToAuthenticated(); } + /// + /// Spawns mice inside the . + /// + /// The type of mice you want to spawn.. + public static void SpawnMice(byte mice = 1) + { + if (mice > SqueakSpawner.mice.Length) + throw new ArgumentOutOfRangeException($"Mouse type must be between 1 and {SqueakSpawner.mice.Length}."); + + SqueakSpawner.NetworksyncSpawn = mice; + SqueakSpawner.SyncMouseSpawn(0, SqueakSpawner.NetworksyncSpawn); + } + /// /// Clears the lazy loading game object cache. /// @@ -369,8 +411,6 @@ internal static void ClearCache() { Item.BaseToItem.Clear(); - LockersValue.RemoveAll(locker => locker == null); - Ragdoll.BasicRagdollToRagdoll.Clear(); Items.Firearm.ItemTypeToFirearmInstance.Clear(); diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 52f339dea5..82a6c73745 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -11,7 +11,9 @@ namespace Exiled.API.Features using System; using System.Collections.Generic; using System.Linq; + using System.Reflection; + using CentralAuth; using CommandSystem; using Exiled.API.Enums; using Exiled.API.Extensions; @@ -140,55 +142,69 @@ public override Vector3 Position /// The userID of the NPC. /// The position to spawn the NPC. /// The spawned. - public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId = "", Vector3? position = null) + public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null) { - GameObject newObject = Object.Instantiate(NetworkManager.singleton.playerPrefab); + GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab); + Npc npc = new(newObject) { - IsVerified = true, IsNPC = true, }; - try - { - npc.ReferenceHub.roleManager.InitializeNewRole(RoleTypeId.None, RoleChangeReason.None); - } - catch (Exception e) + + if (!RecyclablePlayerId.FreeIds.Contains(id) && RecyclablePlayerId._autoIncrement >= id) { - Log.Debug($"Ignore: {e}"); + Log.Warn($"{Assembly.GetCallingAssembly().GetName().Name} tried to spawn an NPC with a duplicate PlayerID. Using auto-incremented ID instead to avoid an ID clash."); + id = new RecyclablePlayerId(true).Value; } - if (RecyclablePlayerId.FreeIds.Contains(id)) + try { - RecyclablePlayerId.FreeIds.RemoveFromQueue(id); + if (userId == PlayerAuthenticationManager.DedicatedId) + { + npc.ReferenceHub.authManager.SyncedUserId = userId; + try + { + npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.DedicatedServer; + } + catch (Exception e) + { + Log.Debug($"Ignore: {e.Message}"); + } + } + else + { + npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.Unverified; + npc.ReferenceHub.authManager._privUserId = userId == string.Empty ? $"Dummy@localhost" : userId; + } } - else if (RecyclablePlayerId._autoIncrement >= id) + catch (Exception e) { - RecyclablePlayerId._autoIncrement = id = RecyclablePlayerId._autoIncrement + 1; + Log.Debug($"Ignore: {e.Message}"); } - FakeConnection fakeConnection = new(id); - NetworkServer.AddPlayerForConnection(fakeConnection, newObject); try { - npc.ReferenceHub.authManager.UserId = string.IsNullOrEmpty(userId) ? $"Dummy@localhost" : userId; + npc.ReferenceHub.roleManager.InitializeNewRole(RoleTypeId.None, RoleChangeReason.None); } catch (Exception e) { - Log.Debug($"Ignore: {e}"); + Log.Debug($"Ignore: {e.Message}"); } + FakeConnection fakeConnection = new(id); + NetworkServer.AddPlayerForConnection(fakeConnection, newObject); + npc.ReferenceHub.nicknameSync.Network_myNickSync = name; Dictionary.Add(newObject, npc); - Timing.CallDelayed( - 0.3f, - () => - { - npc.Role.Set(role, SpawnReason.RoundStart, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory); - }); + Timing.CallDelayed(0.5f, () => + { + npc.Role.Set(role, SpawnReason.RoundStart, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory); + + if (position is not null) + npc.Position = position.Value; + }); - if (position is not null) - Timing.CallDelayed(0.5f, () => npc.Position = position.Value); return npc; } diff --git a/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs b/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs index 1d98dd7eca..e387cf1824 100644 --- a/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs @@ -98,6 +98,11 @@ public int VestEfficacy /// public float StaminaUseMultiplier { get; set; } + /// + /// Gets or sets the stamina regen multiplier. + /// + public float StaminaRegenMultiplier { get; set; } + /// /// Gets how much the users movement speed should be affected when wearing this armor. (higher values = slower movement). /// @@ -129,6 +134,7 @@ internal override void ReadItemInfo(Item item) vestEfficacy = armoritem.VestEfficacy; RemoveExcessOnDrop = armoritem.RemoveExcessOnDrop; StaminaUseMultiplier = armoritem.StaminaUseMultiplier; + StaminaRegenMultiplier = armoritem.StaminaRegenMultiplier; AmmoLimits = armoritem.AmmoLimits; CategoryLimits = armoritem.CategoryLimits; } @@ -144,6 +150,7 @@ protected override void InitializeProperties(ItemBase itemBase) vestEfficacy = armoritem.VestEfficacy; RemoveExcessOnDrop = !armoritem.DontRemoveExcessOnDrop; StaminaUseMultiplier = armoritem._staminaUseMultiplier; + StaminaRegenMultiplier = armoritem.StaminaRegenMultiplier; AmmoLimits = armoritem.AmmoLimits.Select(limit => (ArmorAmmoLimit)limit); CategoryLimits = armoritem.CategoryLimits; } diff --git a/EXILED/Exiled.API/Features/Pickups/Pickup.cs b/EXILED/Exiled.API/Features/Pickups/Pickup.cs index 6c600dd4ca..010b1e52c3 100644 --- a/EXILED/Exiled.API/Features/Pickups/Pickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/Pickup.cs @@ -133,6 +133,7 @@ internal Pickup(ItemType type) public PickupStandardPhysics PhysicsModule { get => Base.PhysicsModule as PickupStandardPhysics; + [Obsolete("Unsafe.")] set { Base.PhysicsModule.DestroyModule(); diff --git a/EXILED/Exiled.API/Features/Pickups/Projectiles/Scp018Projectile.cs b/EXILED/Exiled.API/Features/Pickups/Projectiles/Scp018Projectile.cs index c5549c87f4..798cd04fca 100644 --- a/EXILED/Exiled.API/Features/Pickups/Projectiles/Scp018Projectile.cs +++ b/EXILED/Exiled.API/Features/Pickups/Projectiles/Scp018Projectile.cs @@ -7,7 +7,11 @@ namespace Exiled.API.Features.Pickups.Projectiles { + using System; + using System.Reflection; + using Exiled.API.Interfaces; + using HarmonyLib; using InventorySystem.Items.ThrowableProjectiles; @@ -18,6 +22,9 @@ namespace Exiled.API.Features.Pickups.Projectiles /// public class Scp018Projectile : TimeGrenadeProjectile, IWrapper { + private static FieldInfo maxVelocityField; + private static FieldInfo velocityPerBounceField; + /// /// Initializes a new instance of the class. /// @@ -48,6 +55,7 @@ internal Scp018Projectile() public new Scp018Physics PhysicsModule { get => Base.PhysicsModule as Scp018Physics; + [Obsolete("Unsafe.", true)] set { Base.PhysicsModule.DestroyModule(); @@ -61,7 +69,11 @@ internal Scp018Projectile() public float MaxVelocity { get => PhysicsModule._maxVel; - set => PhysicsModule = new Scp018Physics(Base, PhysicsModule._trail, PhysicsModule._radius, value, PhysicsModule._velPerBounce); + set + { + maxVelocityField ??= AccessTools.Field(typeof(Scp018Physics), nameof(Scp018Physics._maxVel)); + maxVelocityField.SetValue(PhysicsModule, value); + } } /// @@ -70,7 +82,11 @@ public float MaxVelocity public float VelocityPerBounce { get => PhysicsModule._maxVel; - set => PhysicsModule = new Scp018Physics(Base, PhysicsModule._trail, PhysicsModule._radius, MaxVelocity, value); + set + { + velocityPerBounceField ??= AccessTools.Field(typeof(Scp018Physics), nameof(Scp018Physics._velPerBounce)); + velocityPerBounceField.SetValue(PhysicsModule, value); + } } /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 0a2b20b4f2..9e55712eef 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -214,12 +214,13 @@ private set /// /// Gets the 's , can be null. /// - public VoiceModuleBase VoiceModule => RoleManager.CurrentRole is IVoiceRole voiceRole ? voiceRole.VoiceModule : null; + [Obsolete("Use IVoiceRole::VoiceModule instead.")] + public VoiceModuleBase VoiceModule => Role is Roles.IVoiceRole voiceRole ? voiceRole.VoiceModule : null; /// /// Gets the 's , can be null. /// - public PersonalRadioPlayback RadioPlayback => VoiceModule is IRadioVoiceModule radioVoiceModule ? radioVoiceModule.RadioPlayback : null; + public PersonalRadioPlayback RadioPlayback => Role is Roles.IVoiceRole voiceRole ? voiceRole.VoiceModule is IRadioVoiceModule radioVoiceModule ? radioVoiceModule.RadioPlayback : null : null; /// /// Gets the of the player. @@ -793,7 +794,7 @@ public bool IsIntercomMuted /// /// Gets a value indicating whether or not the player is speaking. /// - public bool IsSpeaking => VoiceModule != null && VoiceModule.IsSpeaking; + public bool IsSpeaking => Role is Roles.IVoiceRole voiceRole && voiceRole.VoiceModule.IsSpeaking; /// /// Gets the player's voice color. @@ -805,13 +806,13 @@ public bool IsIntercomMuted /// public VoiceChatChannel VoiceChannel { - get => VoiceModule == null ? VoiceChatChannel.None : VoiceModule.CurrentChannel; + get => Role is Roles.IVoiceRole voiceRole ? voiceRole.VoiceModule.CurrentChannel : VoiceChatChannel.None; set { - if (VoiceModule == null) + if (Role is not Roles.IVoiceRole voiceRole) return; - VoiceModule.CurrentChannel = value; + voiceRole.VoiceModule.CurrentChannel = value; } } @@ -2000,8 +2001,8 @@ public bool RemoveItem(Item item, bool destroy = true) if (item.Serial == Inventory.CurItem.SerialNumber) Inventory.NetworkCurItem = ItemIdentifier.None; + ItemsValue.Remove(item); Inventory.UserInventory.Items.Remove(item.Serial); - typeof(InventoryExtensions).InvokeStaticEvent(nameof(InventoryExtensions.OnItemRemoved), new object[] { ReferenceHub, item.Base, null }); Inventory.SendItemsNextFrame = true; } @@ -2293,7 +2294,7 @@ public void RemoteAdminMessage(string message, bool success = true, string plugi /// if message was send; otherwise, . public bool SendStaffMessage(string message, EncryptedChannelManager.EncryptedChannel channel = EncryptedChannelManager.EncryptedChannel.AdminChat) { - return ReferenceHub.encryptedChannelManager.TrySendMessageToClient("!" + NetId + message, channel); + return ReferenceHub.encryptedChannelManager.TrySendMessageToClient(NetId + "!" + message, channel); } /// @@ -2304,7 +2305,7 @@ public bool SendStaffMessage(string message, EncryptedChannelManager.EncryptedCh /// if message was send; otherwise, . public bool SendStaffPing(string message, EncryptedChannelManager.EncryptedChannel channel = EncryptedChannelManager.EncryptedChannel.AdminChat) { - return ReferenceHub.encryptedChannelManager.TrySendMessageToClient("!0" + message, channel); + return ReferenceHub.encryptedChannelManager.TrySendMessageToClient("0!" + message, channel); } /// @@ -2335,6 +2336,16 @@ public void Broadcast(ushort duration, string message, global::Broadcast.Broadca public void AddAmmo(AmmoType ammoType, ushort amount) => Inventory.ServerAddAmmo(ammoType.GetItemType(), amount); + /// + /// Adds the amount of a specified ammo to player's inventory. + /// + /// A dictionary of ItemType and ushort of ammo and amount. + public void AddAmmo(Dictionary ammo) + { + foreach (KeyValuePair kvp in ammo) + AddAmmo(kvp.Key.GetAmmoType(), kvp.Value); + } + /// /// Adds the amount of a specified ammo type to player's inventory. /// @@ -2578,6 +2589,23 @@ public void ResetCategoryLimit(ItemCategory category) /// If the player has a custom limit for the specific . public bool HasCustomCategoryLimit(ItemCategory category) => CustomCategoryLimits.ContainsKey(category); + /// + /// Grants the player their current role's loadout. + /// + public void GrantLoadout() => GrantLoadout(Role.Type); + + /// + /// Grants a player a role's loadout. + /// + /// The role loadout to give. + public void GrantLoadout(RoleTypeId roleType) + { + InventoryRoleInfo info = roleType.GetInventory(); + + AddItem(info.Items); + AddAmmo(info.Ammo); + } + /// /// Adds an item of the specified type with default durability(ammo/charge) and no mods to the player's inventory. /// @@ -2946,8 +2974,8 @@ public void ClearInventory(bool destroy = true) /// public void ClearItems(bool destroy = true) { - if (CurrentArmor is not null) - CurrentArmor.RemoveExcessOnDrop = true; + if (CurrentArmor is Armor armor) + armor.RemoveExcessOnDrop = false; while (Items.Count > 0) RemoveItem(Items.ElementAt(0), destroy); @@ -3305,6 +3333,14 @@ public void SyncEffects(IEnumerable effects) SyncEffect(effect); } + /// + /// Gets an effect of a player. + /// + /// The to get. + /// The found. + public T GetEffect() + where T : StatusEffectBase => ReferenceHub.playerEffectsController.GetEffect(); + /// /// Gets an instance of by . /// @@ -3572,11 +3608,11 @@ public void RandomTeleport(Type type) nameof(Player) => Dictionary.Values.GetRandomValue(), nameof(Pickup) => Pickup.BaseToPickup.GetRandomValue().Value, nameof(Ragdoll) => Ragdoll.List.GetRandomValue(), - nameof(Locker) => Map.GetRandomLocker(), + nameof(Locker) => Lockers.Locker.Random().Base, nameof(Generator) => Generator.List.GetRandomValue(), nameof(Window) => Window.List.GetRandomValue(), nameof(Scp914) => Scp914.Scp914Controller, - nameof(LockerChamber) => Map.GetRandomLocker().Chambers.GetRandomValue(), + nameof(LockerChamber) => Lockers.Locker.Random().Chambers.GetRandomValue().Base, _ => null, }; diff --git a/EXILED/Exiled.API/Features/Plugin.cs b/EXILED/Exiled.API/Features/Plugin.cs index 0c4b23fd2b..6153cbeee4 100644 --- a/EXILED/Exiled.API/Features/Plugin.cs +++ b/EXILED/Exiled.API/Features/Plugin.cs @@ -7,6 +7,8 @@ namespace Exiled.API.Features { + using System.Linq; + #pragma warning disable SA1402 using System; using System.Collections.Generic; @@ -97,6 +99,8 @@ public virtual void OnEnabled() /// public virtual void OnRegisteringCommands() { + Dictionary> toRegister = new(); + foreach (Type type in Assembly.GetTypes()) { if (type.GetInterface("ICommand") != typeof(ICommand)) @@ -105,28 +109,42 @@ public virtual void OnRegisteringCommands() if (!Attribute.IsDefined(type, typeof(CommandHandlerAttribute))) continue; - foreach (CustomAttributeData customAttributeData in type.CustomAttributes) + foreach (CustomAttributeData customAttributeData in type.GetCustomAttributesData()) { try { if (customAttributeData.AttributeType != typeof(CommandHandlerAttribute)) continue; - Type commandType = (Type)customAttributeData.ConstructorArguments?[0].Value; + Type commandHandlerType = (Type)customAttributeData.ConstructorArguments[0].Value; - if (!Commands.TryGetValue(commandType, out Dictionary typeCommands)) - continue; + ICommand command = GetCommand(type) ?? (ICommand)Activator.CreateInstance(type); - if (!typeCommands.TryGetValue(type, out ICommand command)) - command = (ICommand)Activator.CreateInstance(type); + if (typeof(ParentCommand).IsAssignableFrom(commandHandlerType)) + { + ParentCommand parentCommand = GetCommand(commandHandlerType) as ParentCommand; + + if (parentCommand == null) + { + if (!toRegister.TryGetValue(commandHandlerType, out List list)) + toRegister.Add(commandHandlerType, new() { command }); + else + list.Add(command); + + continue; + } + + parentCommand.RegisterCommand(command); + continue; + } try { - if (commandType == typeof(RemoteAdminCommandHandler)) + if (commandHandlerType == typeof(RemoteAdminCommandHandler)) CommandProcessor.RemoteAdminCommandHandler.RegisterCommand(command); - else if (commandType == typeof(GameConsoleCommandHandler)) + else if (commandHandlerType == typeof(GameConsoleCommandHandler)) GameCore.Console.singleton.ConsoleCommandHandler.RegisterCommand(command); - else if (commandType == typeof(ClientCommandHandler)) + else if (commandHandlerType == typeof(ClientCommandHandler)) QueryProcessor.DotCommandHandler.RegisterCommand(command); } catch (ArgumentException e) @@ -141,7 +159,7 @@ public virtual void OnRegisteringCommands() } } - Commands[commandType][type] = command; + Commands[commandHandlerType][type] = command; } catch (Exception exception) { @@ -149,6 +167,39 @@ public virtual void OnRegisteringCommands() } } } + + foreach (KeyValuePair> kvp in toRegister) + { + ParentCommand parentCommand = GetCommand(kvp.Key) as ParentCommand; + + foreach (ICommand command in kvp.Value) + parentCommand.RegisterCommand(command); + } + } + + /// + /// Gets a command by it's type. + /// + /// 's type. + /// 's type. Defines in which command handler command is registered. + /// A . May be if command is not registered. + public ICommand GetCommand(Type type, Type commandHandler = null) + { + if (type.GetInterface("ICommand") != typeof(ICommand)) + return null; + + if (commandHandler != null) + { + if (!Commands.TryGetValue(commandHandler, out Dictionary commands)) + return null; + + if (!commands.TryGetValue(type, out ICommand command)) + return null; + + return command; + } + + return Commands.Keys.Select(commandHandlerType => GetCommand(type, commandHandlerType)).FirstOrDefault(command => command != null); } /// diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 4ca71d8c8a..4fe6151bde 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -11,20 +11,21 @@ namespace Exiled.API.Features.Roles using System.Reflection; using Exiled.API.Features.Pools; - using HarmonyLib; using PlayerRoles; using PlayerRoles.FirstPersonControl; - + using PlayerRoles.Ragdolls; + using PlayerRoles.Spectating; + using PlayerRoles.Visibility; + using PlayerRoles.Voice; using PlayerStatsSystem; using RelativePositioning; - using UnityEngine; /// /// Defines a role that represents an fpc class. /// - public abstract class FpcRole : Role + public abstract class FpcRole : Role, IVoiceRole { private static FieldInfo enableFallDamageField; private bool isUsingStamina = true; @@ -243,6 +244,30 @@ public bool IsNoclipEnabled set => Owner.ReferenceHub.playerStats.GetModule().SetFlag(AdminFlags.Noclip, value); } + /// + /// Gets or sets a prefab ragdoll for this role. + /// + public BasicRagdoll Ragdoll + { + get => FirstPersonController.Ragdoll; + set => FirstPersonController.Ragdoll = value; + } + + /// + /// Gets a voice module for this role. + /// + public VoiceModuleBase VoiceModule => FirstPersonController.VoiceModule; + + /// + /// Gets a for this role. + /// + public VisibilityController VisibilityController => FirstPersonController.VisibilityController; + + /// + /// Gets a for this role. + /// + public SpectatableModuleBase SpectatableModuleBase => FirstPersonController.SpectatorModule; + /// /// Resets the 's stamina. /// diff --git a/EXILED/Exiled.API/Features/Roles/IVoiceRole.cs b/EXILED/Exiled.API/Features/Roles/IVoiceRole.cs new file mode 100644 index 0000000000..74e431fcd8 --- /dev/null +++ b/EXILED/Exiled.API/Features/Roles/IVoiceRole.cs @@ -0,0 +1,22 @@ +๏ปฟ// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Roles +{ + using PlayerRoles.Voice; + + /// + /// Interface for all roles with . + /// + public interface IVoiceRole + { + /// + /// Gets the of the role. + /// + public VoiceModuleBase VoiceModule { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Roles/NoneRole.cs b/EXILED/Exiled.API/Features/Roles/NoneRole.cs index d97cd448e5..b890ddb23d 100644 --- a/EXILED/Exiled.API/Features/Roles/NoneRole.cs +++ b/EXILED/Exiled.API/Features/Roles/NoneRole.cs @@ -8,13 +8,14 @@ namespace Exiled.API.Features.Roles { using PlayerRoles; + using PlayerRoles.Voice; using NoneGameRole = PlayerRoles.NoneRole; /// /// Defines a role that represents players with no role. /// - public class NoneRole : Role + public class NoneRole : Role, IVoiceRole { /// /// Initializes a new instance of the class. @@ -27,5 +28,8 @@ internal NoneRole(PlayerRoleBase baseRole) /// public override RoleTypeId Type { get; } = RoleTypeId.None; + + /// + public VoiceModuleBase VoiceModule => (Base as NoneGameRole) !.VoiceModule; } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Roles/Role.cs b/EXILED/Exiled.API/Features/Roles/Role.cs index 96f7a3b687..bc9f94d20d 100644 --- a/EXILED/Exiled.API/Features/Roles/Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Role.cs @@ -10,19 +10,17 @@ namespace Exiled.API.Features.Roles using System; using Enums; - using Exiled.API.Features.Core; using Exiled.API.Features.Spawn; using Exiled.API.Interfaces; using Extensions; - using PlayerRoles; using PlayerRoles.PlayableScps.Scp049.Zombies; - using UnityEngine; using FilmmakerGameRole = PlayerRoles.Filmmaker.FilmmakerRole; using HumanGameRole = PlayerRoles.HumanRole; + using NoneGameRole = PlayerRoles.NoneRole; using OverwatchGameRole = PlayerRoles.Spectating.OverwatchRole; using Scp049GameRole = PlayerRoles.PlayableScps.Scp049.Scp049Role; using Scp079GameRole = PlayerRoles.PlayableScps.Scp079.Scp079Role; @@ -229,7 +227,8 @@ public virtual void Set(RoleTypeId newRole, SpawnReason reason, RoleSpawnFlags s SpectatorGameRole spectatorRole => new SpectatorRole(spectatorRole), HumanGameRole humanRole => new HumanRole(humanRole), FilmmakerGameRole filmmakerRole => new FilmMakerRole(filmmakerRole), - _ => new NoneRole(role), + NoneGameRole noneRole => new NoneRole(noneRole), + _ => null, }; } } diff --git a/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs b/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs index 1596ad83cf..f2196a0a7c 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs @@ -105,7 +105,7 @@ public float SimulatedStare /// /// Gets the that SCP-049-2 is currently consuming. Will be if is . /// - public Ragdoll RagdollConsuming => Ragdoll.Get(ConsumeAbility.CurRagdoll); + public Ragdoll RagdollConsuming => Features.Ragdoll.Get(ConsumeAbility.CurRagdoll); /// /// Gets the amount of time in between SCP-049-2 attacks. diff --git a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs index eff90bc44a..2e1384e6e2 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp049Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp049Role.cs @@ -111,7 +111,7 @@ internal Scp049Role(Scp049GameRole baseRole) /// /// Gets the ragdoll that is currently being revived by SCP-049. Will be if is . /// - public Ragdoll RecallingRagdoll => Ragdoll.Get(ResurrectAbility.CurRagdoll); + public Ragdoll RecallingRagdoll => Features.Ragdoll.Get(ResurrectAbility.CurRagdoll); /// /// Gets all the dead zombies. @@ -255,7 +255,7 @@ public bool Resurrect(Player player) HumeShieldModuleBase humeShield = ResurrectAbility.CastRole.HumeShieldModule; humeShield.HsCurrent = Mathf.Min(humeShield.HsCurrent + 100f, humeShield.HsMax); - return Resurrect(Ragdoll.GetLast(player)); + return Resurrect(Features.Ragdoll.GetLast(player)); } /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs index a3de5059cb..05334e3089 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp079Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp079Role.cs @@ -22,6 +22,7 @@ namespace Exiled.API.Features.Roles using PlayerRoles.PlayableScps.Scp079.Pinging; using PlayerRoles.PlayableScps.Scp079.Rewards; using PlayerRoles.Subroutines; + using PlayerRoles.Voice; using RelativePositioning; using Utils.NonAllocLINQ; @@ -32,7 +33,7 @@ namespace Exiled.API.Features.Roles /// /// Defines a role that represents SCP-079. /// - public class Scp079Role : Role, ISubroutinedScpRole, ISpawnableScp + public class Scp079Role : Role, ISubroutinedScpRole, ISpawnableScp, IVoiceRole { /// /// Initializes a new instance of the class. @@ -374,6 +375,9 @@ public float Scp2176LostTime /// public float EnergyRegenerationSpeed => AuxManager.RegenSpeed; + /// + public VoiceModuleBase VoiceModule => Base.VoiceModule; + /// /// Gets the game . /// diff --git a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs index 0d23e48ba3..89dcefa153 100644 --- a/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs +++ b/EXILED/Exiled.API/Features/Roles/Scp3114Role.cs @@ -7,6 +7,7 @@ namespace Exiled.API.Features.Roles { + using System; using System.Collections.Generic; using Exiled.API.Enums; @@ -149,24 +150,34 @@ public RoleTypeId StolenRole get => Identity.CurIdentity.StolenRole; set { - if (Ragdoll is null) + if (IdentityRagdoll is null) return; - Ragdoll.Role = value; - Identity.ServerResendIdentity(); + IdentityRagdoll.Role = value; + UpdateIdentity(); } } /// /// Gets or sets the SCP-3114's Ragdoll used for it's FakeIdentity. /// - public Ragdoll Ragdoll + [Obsolete("Ragdoll in Role now has other meaning. Use IdentityRagdoll instead.")] + public new Ragdoll Ragdoll + { + get => IdentityRagdoll; + set => IdentityRagdoll = value; + } + + /// + /// Gets or sets the SCP-3114's Ragdoll used for it's FakeIdentity. + /// + public Ragdoll IdentityRagdoll { get => Ragdoll.Get(Identity.CurIdentity.Ragdoll); set { Identity.CurIdentity.Ragdoll = value?.Base; - Identity.ServerResendIdentity(); + UpdateIdentity(); } } @@ -179,7 +190,7 @@ public byte UnitId set { Identity.CurIdentity.UnitNameId = value; - Identity.ServerResendIdentity(); + UpdateIdentity(); } } @@ -192,7 +203,7 @@ public DisguiseStatus DisguiseStatus set { Identity.CurIdentity.Status = value; - Identity.ServerResendIdentity(); + UpdateIdentity(); } } @@ -202,7 +213,11 @@ public DisguiseStatus DisguiseStatus public float DisguiseDuration { get => Identity._disguiseDurationSeconds; - set => Identity._disguiseDurationSeconds = value; + set + { + Identity._disguiseDurationSeconds = value; + UpdateIdentity(); + } } /// @@ -219,13 +234,18 @@ public float WarningTime /// internal DanceType DanceType { get; set; } + /// + /// Updates the identity of SCP-3114. + /// + public void UpdateIdentity() => Identity.ServerResendIdentity(); + /// /// Reset Scp3114 FakeIdentity. /// public void ResetIdentity() { Identity.CurIdentity.Reset(); - Identity.ServerResendIdentity(); + UpdateIdentity(); } /// diff --git a/EXILED/Exiled.API/Features/Roles/SpectatorRole.cs b/EXILED/Exiled.API/Features/Roles/SpectatorRole.cs index cdaf0fe864..3bb2f052f3 100644 --- a/EXILED/Exiled.API/Features/Roles/SpectatorRole.cs +++ b/EXILED/Exiled.API/Features/Roles/SpectatorRole.cs @@ -10,7 +10,7 @@ namespace Exiled.API.Features.Roles using System; using PlayerRoles; - + using PlayerRoles.Voice; using UnityEngine; using SpectatorGameRole = PlayerRoles.Spectating.SpectatorRole; @@ -18,7 +18,7 @@ namespace Exiled.API.Features.Roles /// /// Defines a role that represents a spectator. /// - public class SpectatorRole : Role + public class SpectatorRole : Role, IVoiceRole { /// /// Initializes a new instance of the class. @@ -70,5 +70,8 @@ public Player SpectatedPlayer /// Gets the game . /// public new SpectatorGameRole Base { get; } + + /// + public VoiceModuleBase VoiceModule => Base.VoiceModule; } } diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index b1b05e8c19..5dea5ee84c 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -11,6 +11,8 @@ namespace Exiled.API.Features using System.Collections.Generic; using System.Reflection; + using Exiled.API.Enums; + using GameCore; using Interfaces; diff --git a/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs b/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs new file mode 100644 index 0000000000..ecc4341279 --- /dev/null +++ b/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Spawn +{ + using System; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Features.Lockers; + using UnityEngine; + using YamlDotNet.Serialization; + + /// + /// Handles the spawn point inside a locker. + /// + public class LockerSpawnPoint : SpawnPoint + { + /// + /// Gets or sets the zone where the locker is located. + /// + public ZoneType Zone { get; set; } = ZoneType.Unspecified; + + /// + /// Gets or sets a value indicating whether to use a random locker chamber's position for spawning. + /// If , will be ignored. + /// + public bool UseChamber { get; set; } + + /// + /// Gets or sets the offset position within the locker where the spawn point is located, relative to the locker's origin. + /// + public Vector3 Offset { get; set; } = Vector3.zero; + + /// + /// Gets or sets the type of the . + /// + public LockerType Type { get; set; } = LockerType.Unknow; + + /// + public override float Chance { get; set; } + + /// + [YamlIgnore] + public override string Name + { + get => Zone.ToString(); + set => throw new InvalidOperationException("The name of this type of SpawnPoint cannot be changed."); + } + + /// + [YamlIgnore] + public override Vector3 Position + { + get + { + Locker foundLocker = Locker.Random(Zone, Type) ?? throw new NullReferenceException("No locker found in the specified zone."); + + // If UseChamber is true, use a random chamber's position. + if (UseChamber) + return foundLocker.RandomChamberPosition; + + // Otherwise, use the Offset if provided, or the locker's position. + return Offset != Vector3.zero ? foundLocker.Transform.TransformPoint(Offset) : foundLocker.Position; + } + set => throw new InvalidOperationException("The position of this type of SpawnPoint cannot be changed."); + } + } +} diff --git a/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs new file mode 100644 index 0000000000..92e24e712f --- /dev/null +++ b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Spawn +{ + using System; + + using Exiled.API.Enums; + + using UnityEngine; + + using YamlDotNet.Serialization; + + /// + /// Represents a spawn point within a specific room in the game. + /// + public class RoomSpawnPoint : SpawnPoint + { + /// + /// Gets or sets the room type used for this spawn. + /// + public RoomType Room { get; set; } + + /// + /// Gets or sets the offset position within the room where the spawn point is located, relative to the room's origin. + /// + public Vector3 Offset { get; set; } = Vector3.zero; + + /// + public override float Chance { get; set; } + + /// + [YamlIgnore] + public override string Name + { + get => Room.ToString(); + set => throw new InvalidOperationException("The name of this type of SpawnPoint cannot be changed."); + } + + /// + [YamlIgnore] + public override Vector3 Position + { + get + { + Room roomInstance = Features.Room.Get(Room) ?? throw new InvalidOperationException("The room instance could not be found."); + + return Offset != Vector3.zero ? roomInstance.transform.TransformPoint(Offset) : roomInstance.Position; + } + set => throw new InvalidOperationException("The position of this type of SpawnPoint cannot be changed."); + } + } +} diff --git a/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs b/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs index ee8ddef4ba..4cdf3345b6 100644 --- a/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs +++ b/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs @@ -34,10 +34,20 @@ public class SpawnProperties /// public List RoleSpawnPoints { get; set; } = new(); + /// + /// Gets or sets a of possible room-based spawn points. + /// + public List RoomSpawnPoints { get; set; } = new(); + + /// + /// Gets or sets a of possible locker-based spawn points. + /// + public List LockerSpawnPoints { get; set; } = new(); + /// /// Counts how many spawn points are in this instance. /// /// How many spawn points there are. - public int Count() => DynamicSpawnPoints.Count + StaticSpawnPoints.Count + RoleSpawnPoints.Count; + public int Count() => DynamicSpawnPoints.Count + StaticSpawnPoints.Count + RoleSpawnPoints.Count + RoomSpawnPoints.Count + LockerSpawnPoints.Count; } } \ No newline at end of file diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs index 23359d6b09..c8598a98bc 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs @@ -619,6 +619,7 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) spawned++; +#pragma warning disable CS0618 // Type or member is obsolete \\ TODO: REMOVE THIS if (spawnPoint is DynamicSpawnPoint dynamicSpawnPoint && dynamicSpawnPoint.Location == SpawnLocationType.InsideLocker) { for (int i = 0; i < 50; i++) @@ -660,16 +661,18 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) } Vector3 position = chamber._spawnpoint.transform.position; - Spawn(position, null); - Log.Debug($"Spawned {Name} at {position} ({spawnPoint.Name})"); + Pickup? pickup = Spawn(position, null); + if (pickup?.Base is BaseFirearmPickup firearmPickup && this is CustomWeapon customWeapon) + { + firearmPickup.Status = new FirearmStatus(customWeapon.ClipSize, firearmPickup.Status.Flags, firearmPickup.Status.Attachments); + firearmPickup.NetworkStatus = firearmPickup.Status; + } + + Log.Debug($"Spawned {Name} at {position} ({spawnPoint.Name})"); break; } } - else if (spawnPoint is RoleSpawnPoint roleSpawnPoint) - { - Spawn(roleSpawnPoint.Role.GetRandomSpawnLocation().Position, null); - } else { Pickup? pickup = Spawn(spawnPoint.Position, null); @@ -681,6 +684,7 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) Log.Debug($"Spawned {Name} at {spawnPoint.Position} ({spawnPoint.Name})"); } +#pragma warning restore CS0618 // Type or member is obsolete } return spawned; @@ -694,8 +698,8 @@ public virtual void SpawnAll() if (SpawnProperties is null) return; - // This will go over each spawn property type (static, dynamic and role) to try and spawn the item. - // It will attempt to spawn in role-based locations, and then dynamic ones, and finally static. + // This will go over each spawn property type (static, dynamic, role-based, room-based, and locker-based) to try and spawn the item. + // It will attempt to spawn in role-based locations, then dynamic ones, followed by room-based, locker-based, and finally static. // Math.Min is used here to ensure that our recursive Spawn() calls do not result in exceeding the spawn limit config. // This is the same as: // int spawned = 0; @@ -703,8 +707,12 @@ public virtual void SpawnAll() // if (spawned < SpawnProperties.Limit) // spawned += Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit - spawned); // if (spawned < SpawnProperties.Limit) + // spawned += Spawn(SpawnProperties.RoomSpawnPoints, SpawnProperties.Limit - spawned); + // if (spawned < SpawnProperties.Limit) + // spawned += Spawn(SpawnProperties.LockerSpawnPoints, SpawnProperties.Limit - spawned); + // if (spawned < SpawnProperties.Limit) // Spawn(SpawnProperties.StaticSpawnPoints, SpawnProperties.Limit - spawned); - Spawn(SpawnProperties.StaticSpawnPoints, Math.Min(0, SpawnProperties.Limit - Math.Min(0, Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit) - Spawn(SpawnProperties.RoleSpawnPoints, SpawnProperties.Limit)))); + Spawn(SpawnProperties.StaticSpawnPoints, Math.Min(SpawnProperties.Limit, SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.RoleSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.RoomSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Spawn(SpawnProperties.LockerSpawnPoints, SpawnProperties.Limit)))))); } /// diff --git a/EXILED/Exiled.CustomItems/Commands/Info.cs b/EXILED/Exiled.CustomItems/Commands/Info.cs index f0de9f1a99..0b6c6e5493 100644 --- a/EXILED/Exiled.CustomItems/Commands/Info.cs +++ b/EXILED/Exiled.CustomItems/Commands/Info.cs @@ -68,7 +68,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s .Append("- ").AppendLine(item?.Description) .AppendLine(item?.Type.ToString()) .Append("- Spawn Limit: ").AppendLine(item?.SpawnProperties?.Limit.ToString()).AppendLine() - .Append("[Spawn Locations (").Append(item?.SpawnProperties?.DynamicSpawnPoints.Count + item?.SpawnProperties?.StaticSpawnPoints.Count).AppendLine(")]"); + .Append("[Spawn Locations (").Append(item?.SpawnProperties?.Count()).AppendLine(")]"); foreach (DynamicSpawnPoint spawnPoint in item?.SpawnProperties?.DynamicSpawnPoints!) message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); @@ -76,6 +76,15 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s foreach (StaticSpawnPoint spawnPoint in item.SpawnProperties.StaticSpawnPoints) message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + foreach (RoleSpawnPoint spawnPoint in item.SpawnProperties.RoleSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + + foreach (LockerSpawnPoint spawnPoint in item.SpawnProperties.LockerSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + + foreach (RoomSpawnPoint spawnPoint in item.SpawnProperties.RoomSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + response = StringBuilderPool.Pool.ToStringReturn(message); return true; } diff --git a/EXILED/Exiled.CustomItems/Commands/List/List.cs b/EXILED/Exiled.CustomItems/Commands/List/List.cs index 9ee1bcf628..98f3e2d12d 100644 --- a/EXILED/Exiled.CustomItems/Commands/List/List.cs +++ b/EXILED/Exiled.CustomItems/Commands/List/List.cs @@ -45,7 +45,14 @@ public override void LoadGeneratedCommands() /// protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) { - response = $"Invalid subcommand! Available: registered, insideinventories"; + if (arguments.IsEmpty() && TryGetCommand(Registered.Instance.Command, out ICommand command)) + { + command.Execute(arguments, sender, out response); + response += $"\nTo view custom items in players' inventories, use the command: {string.Join(" ", arguments.Array)} insideinventories"; + return true; + } + + response = "Invalid subcommand! Available: registered, insideinventories"; return false; } } diff --git a/EXILED/Exiled.CustomItems/CustomItems.cs b/EXILED/Exiled.CustomItems/CustomItems.cs index 8a8e15b368..855186c002 100644 --- a/EXILED/Exiled.CustomItems/CustomItems.cs +++ b/EXILED/Exiled.CustomItems/CustomItems.cs @@ -19,7 +19,7 @@ namespace Exiled.CustomItems /// public class CustomItems : Plugin { - private RoundHandler? roundHandler; + private MapHandler? roundHandler; private PlayerHandler? playerHandler; private Harmony? harmony; @@ -32,10 +32,10 @@ public class CustomItems : Plugin public override void OnEnabled() { Instance = this; - roundHandler = new RoundHandler(); + roundHandler = new MapHandler(); playerHandler = new PlayerHandler(); - Exiled.Events.Handlers.Server.RoundStarted += roundHandler.OnRoundStarted; + Exiled.Events.Handlers.Map.Generated += roundHandler.OnMapGenerated; Exiled.Events.Handlers.Player.ChangingItem += playerHandler.OnChangingItem; @@ -50,7 +50,7 @@ public override void OnEnabled() /// public override void OnDisabled() { - Exiled.Events.Handlers.Server.RoundStarted -= roundHandler!.OnRoundStarted; + Exiled.Events.Handlers.Map.Generated -= roundHandler!.OnMapGenerated; Exiled.Events.Handlers.Player.ChangingItem -= playerHandler!.OnChangingItem; diff --git a/EXILED/Exiled.CustomItems/Events/MapHandler.cs b/EXILED/Exiled.CustomItems/Events/MapHandler.cs new file mode 100644 index 0000000000..4e0c2442f9 --- /dev/null +++ b/EXILED/Exiled.CustomItems/Events/MapHandler.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.CustomItems.Events +{ + using Exiled.CustomItems.API.Features; + using MEC; + + /// + /// Event Handlers for the CustomItem API. + /// + internal sealed class MapHandler + { + /// + public void OnMapGenerated() + { + Timing.CallDelayed(0.5f, () => // Delay its necessary for the spawnpoints of lockers and rooms to be generated. + { + foreach (CustomItem customItem in CustomItem.Registered) + customItem?.SpawnAll(); + }); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.CustomItems/Events/RoundHandler.cs b/EXILED/Exiled.CustomItems/Events/RoundHandler.cs deleted file mode 100644 index ebc8c4648f..0000000000 --- a/EXILED/Exiled.CustomItems/Events/RoundHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.CustomItems.Events -{ - using Exiled.CustomItems.API.Features; - - /// - /// Event Handlers for the CustomItem API. - /// - internal sealed class RoundHandler - { - /// - public void OnRoundStarted() - { - foreach (CustomItem customItem in CustomItem.Registered) - customItem?.SpawnAll(); - } - } -} \ 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 61f88ec2cb..29151b1f4a 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -840,6 +840,16 @@ protected Vector3 GetSpawnPosition() } } + if (SpawnProperties.RoomSpawnPoints.Count > 0) + { + foreach ((float chance, Vector3 pos) in SpawnProperties.RoomSpawnPoints) + { + double r = Loader.Random.NextDouble() * 100; + if (r <= chance) + return pos; + } + } + return Vector3.zero; } diff --git a/EXILED/Exiled.CustomRoles/Commands/List/List.cs b/EXILED/Exiled.CustomRoles/Commands/List/List.cs index 919105a321..661cecb498 100644 --- a/EXILED/Exiled.CustomRoles/Commands/List/List.cs +++ b/EXILED/Exiled.CustomRoles/Commands/List/List.cs @@ -45,7 +45,14 @@ public override void LoadGeneratedCommands() /// protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) { - response = "Invalid subcommand! Available: registered."; + if (arguments.IsEmpty() && TryGetCommand(Registered.Instance.Command, out ICommand command)) + { + command.Execute(arguments, sender, out response); + response += $"\nTo view all abilities registered use command: {string.Join(" ", arguments.Array)} abilities"; + return true; + } + + response = "Invalid subcommand! Available: registered, abilities"; return false; } } diff --git a/EXILED/Exiled.Events/Commands/Config/EConfig.cs b/EXILED/Exiled.Events/Commands/Config/EConfig.cs index 120ed46619..6b6ead4b60 100644 --- a/EXILED/Exiled.Events/Commands/Config/EConfig.cs +++ b/EXILED/Exiled.Events/Commands/Config/EConfig.cs @@ -17,14 +17,6 @@ namespace Exiled.Events.Commands.Config [CommandHandler(typeof(GameConsoleCommandHandler))] public class EConfig : ParentCommand { - /// - /// Initializes a new instance of the class. - /// - public EConfig() - { - LoadGeneratedCommands(); - } - /// public override string Command { get; } = "econfig"; @@ -37,8 +29,6 @@ public EConfig() /// public override void LoadGeneratedCommands() { - RegisterCommand(Merge.Instance); - RegisterCommand(Split.Instance); } /// diff --git a/EXILED/Exiled.Events/Commands/Config/Merge.cs b/EXILED/Exiled.Events/Commands/Config/Merge.cs index dc90cad8ec..7bad3a00e4 100644 --- a/EXILED/Exiled.Events/Commands/Config/Merge.cs +++ b/EXILED/Exiled.Events/Commands/Config/Merge.cs @@ -20,6 +20,7 @@ namespace Exiled.Events.Commands.Config /// /// The config merge command. /// + [CommandHandler(typeof(EConfig))] public class Merge : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Config/Split.cs b/EXILED/Exiled.Events/Commands/Config/Split.cs index ab2f1a577c..ad42f3024a 100644 --- a/EXILED/Exiled.Events/Commands/Config/Split.cs +++ b/EXILED/Exiled.Events/Commands/Config/Split.cs @@ -20,6 +20,7 @@ namespace Exiled.Events.Commands.Config /// /// The config split command. /// + [CommandHandler(typeof(EConfig))] public class Split : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Hub/Hub.cs b/EXILED/Exiled.Events/Commands/Hub/Hub.cs new file mode 100644 index 0000000000..ba36480fa5 --- /dev/null +++ b/EXILED/Exiled.Events/Commands/Hub/Hub.cs @@ -0,0 +1,51 @@ +๏ปฟ// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled 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 new file mode 100644 index 0000000000..ad2f59223d --- /dev/null +++ b/EXILED/Exiled.Events/Commands/Hub/HubApi/ApiProvider.cs @@ -0,0 +1,64 @@ +๏ปฟ// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled 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 new file mode 100644 index 0000000000..cac15a689a --- /dev/null +++ b/EXILED/Exiled.Events/Commands/Hub/HubApi/Models/HubPlugin.cs @@ -0,0 +1,35 @@ +๏ปฟ// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled 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 new file mode 100644 index 0000000000..bdcd7e18f4 --- /dev/null +++ b/EXILED/Exiled.Events/Commands/Hub/Install.cs @@ -0,0 +1,116 @@ +๏ปฟ// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled 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; + } + + Log.Info($"Downloading release \"{releaseToDownload.TagName}\". Found {releaseToDownload.Assets.Length} asset(s) to download."); + + foreach (ReleaseAsset asset in releaseToDownload.Assets) + { + 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/Commands/PluginManager/Disable.cs b/EXILED/Exiled.Events/Commands/PluginManager/Disable.cs index 998a81cdfb..81d3bdffd5 100644 --- a/EXILED/Exiled.Events/Commands/PluginManager/Disable.cs +++ b/EXILED/Exiled.Events/Commands/PluginManager/Disable.cs @@ -17,6 +17,7 @@ namespace Exiled.Events.Commands.PluginManager /// /// The command to disable a plugin. /// + [CommandHandler(typeof(PluginManager))] public sealed class Disable : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/PluginManager/Enable.cs b/EXILED/Exiled.Events/Commands/PluginManager/Enable.cs index 5c0201b647..4e400fe81c 100644 --- a/EXILED/Exiled.Events/Commands/PluginManager/Enable.cs +++ b/EXILED/Exiled.Events/Commands/PluginManager/Enable.cs @@ -21,6 +21,7 @@ namespace Exiled.Events.Commands.PluginManager /// /// The command to enable a plugin. /// + [CommandHandler(typeof(PluginManager))] public sealed class Enable : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/PluginManager/Patches.cs b/EXILED/Exiled.Events/Commands/PluginManager/Patches.cs index 9543e73aab..61d729f822 100644 --- a/EXILED/Exiled.Events/Commands/PluginManager/Patches.cs +++ b/EXILED/Exiled.Events/Commands/PluginManager/Patches.cs @@ -23,6 +23,7 @@ namespace Exiled.Events.Commands.PluginManager /// /// The command to show all the patches done by plugins. /// + [CommandHandler(typeof(PluginManager))] public sealed class Patches : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/PluginManager/PluginManager.cs b/EXILED/Exiled.Events/Commands/PluginManager/PluginManager.cs index ba778d5f43..2a4a550b40 100644 --- a/EXILED/Exiled.Events/Commands/PluginManager/PluginManager.cs +++ b/EXILED/Exiled.Events/Commands/PluginManager/PluginManager.cs @@ -18,14 +18,6 @@ namespace Exiled.Events.Commands.PluginManager [CommandHandler(typeof(GameConsoleCommandHandler))] public class PluginManager : ParentCommand { - /// - /// Initializes a new instance of the class. - /// - public PluginManager() - { - LoadGeneratedCommands(); - } - /// public override string Command { get; } = "pluginmanager"; @@ -38,10 +30,6 @@ public PluginManager() /// public override void LoadGeneratedCommands() { - RegisterCommand(Show.Instance); - RegisterCommand(Enable.Instance); - RegisterCommand(Disable.Instance); - RegisterCommand(Patches.Instance); } /// diff --git a/EXILED/Exiled.Events/Commands/PluginManager/Show.cs b/EXILED/Exiled.Events/Commands/PluginManager/Show.cs index b703d44fcd..cd73799329 100644 --- a/EXILED/Exiled.Events/Commands/PluginManager/Show.cs +++ b/EXILED/Exiled.Events/Commands/PluginManager/Show.cs @@ -24,6 +24,7 @@ namespace Exiled.Events.Commands.PluginManager /// /// The command to show all plugins. /// + [CommandHandler(typeof(PluginManager))] public sealed class Show : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/All.cs b/EXILED/Exiled.Events/Commands/Reload/All.cs index 124114e7e5..a46af1f5e2 100644 --- a/EXILED/Exiled.Events/Commands/Reload/All.cs +++ b/EXILED/Exiled.Events/Commands/Reload/All.cs @@ -14,6 +14,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload all command. /// + [CommandHandler(typeof(Reload))] public class All : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/Configs.cs b/EXILED/Exiled.Events/Commands/Reload/Configs.cs index 81ab11e792..2c6ed0a27c 100644 --- a/EXILED/Exiled.Events/Commands/Reload/Configs.cs +++ b/EXILED/Exiled.Events/Commands/Reload/Configs.cs @@ -20,6 +20,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload configs command. /// + [CommandHandler(typeof(Reload))] public class Configs : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/GamePlay.cs b/EXILED/Exiled.Events/Commands/Reload/GamePlay.cs index babd4c5bd2..02a02460a3 100644 --- a/EXILED/Exiled.Events/Commands/Reload/GamePlay.cs +++ b/EXILED/Exiled.Events/Commands/Reload/GamePlay.cs @@ -18,6 +18,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload gameplay command. /// + [CommandHandler(typeof(Reload))] public class GamePlay : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/Permissions.cs b/EXILED/Exiled.Events/Commands/Reload/Permissions.cs index 1f3943a82e..a0e9b9ec42 100644 --- a/EXILED/Exiled.Events/Commands/Reload/Permissions.cs +++ b/EXILED/Exiled.Events/Commands/Reload/Permissions.cs @@ -17,6 +17,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload permissions command. /// + [CommandHandler(typeof(Reload))] public class Permissions : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/Plugins.cs b/EXILED/Exiled.Events/Commands/Reload/Plugins.cs index 1a1c144722..f41d6027c0 100644 --- a/EXILED/Exiled.Events/Commands/Reload/Plugins.cs +++ b/EXILED/Exiled.Events/Commands/Reload/Plugins.cs @@ -19,6 +19,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload plugins command. /// + [CommandHandler(typeof(Reload))] public class Plugins : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/Reload.cs b/EXILED/Exiled.Events/Commands/Reload/Reload.cs index bf139a7fd0..b68927f63f 100644 --- a/EXILED/Exiled.Events/Commands/Reload/Reload.cs +++ b/EXILED/Exiled.Events/Commands/Reload/Reload.cs @@ -18,14 +18,6 @@ namespace Exiled.Events.Commands.Reload [CommandHandler(typeof(GameConsoleCommandHandler))] public class Reload : ParentCommand { - /// - /// Initializes a new instance of the class. - /// - public Reload() - { - LoadGeneratedCommands(); - } - /// public override string Command { get; } = "reload"; @@ -38,13 +30,6 @@ public Reload() /// public override void LoadGeneratedCommands() { - RegisterCommand(All.Instance); - RegisterCommand(Configs.Instance); - RegisterCommand(Translations.Instance); - RegisterCommand(Plugins.Instance); - RegisterCommand(GamePlay.Instance); - RegisterCommand(RemoteAdmin.Instance); - RegisterCommand(Permissions.Instance); } /// diff --git a/EXILED/Exiled.Events/Commands/Reload/RemoteAdmin.cs b/EXILED/Exiled.Events/Commands/Reload/RemoteAdmin.cs index 4777a7e4b0..7c21428b3e 100644 --- a/EXILED/Exiled.Events/Commands/Reload/RemoteAdmin.cs +++ b/EXILED/Exiled.Events/Commands/Reload/RemoteAdmin.cs @@ -18,6 +18,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload remoteadmin command. /// + [CommandHandler(typeof(Reload))] public class RemoteAdmin : ICommand { /// diff --git a/EXILED/Exiled.Events/Commands/Reload/Translations.cs b/EXILED/Exiled.Events/Commands/Reload/Translations.cs index 68d6f66986..621a7036bb 100644 --- a/EXILED/Exiled.Events/Commands/Reload/Translations.cs +++ b/EXILED/Exiled.Events/Commands/Reload/Translations.cs @@ -20,6 +20,7 @@ namespace Exiled.Events.Commands.Reload /// /// The reload translations command. /// + [CommandHandler(typeof(Reload))] public class Translations : ICommand { /// diff --git a/EXILED/Exiled.Events/Config.cs b/EXILED/Exiled.Events/Config.cs index 006be02b2a..b141cd17ee 100644 --- a/EXILED/Exiled.Events/Config.cs +++ b/EXILED/Exiled.Events/Config.cs @@ -80,6 +80,12 @@ public sealed class Config : IConfig [Description("Indicates whether thrown keycards can affect doors that don't require any permissions")] public bool CanKeycardThrowAffectDoors { get; set; } = false; + /// + /// Gets or sets a value indicating whether the SCP079 will recontained if there are no SCPs left. + /// + [Description("Indicates whether the SCP079 will recontained if there are no SCPs left.")] + public bool RecontainScp079IfNoScpsLeft { get; set; } = true; + /// /// Gets or sets a value indicating whether configs has to be reloaded every time a round restarts. /// @@ -90,7 +96,7 @@ public sealed class Config : IConfig /// Gets or sets a value indicating whether translations has to be reloaded every time a round restarts. /// [Description("Indicates whether translations has to be reloaded every round restart")] - public bool ShouldReloadTranslationsAtRoundRestart { get; set; } + public bool ShouldReloadTranslationsAtRoundRestart { get; set; } = false; /// /// Gets a value indicating whether bans should be logged or not. diff --git a/EXILED/Exiled.Events/EventArgs/Item/KeycardInteractingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/KeycardInteractingEventArgs.cs index 65f7a95c79..181f7c7eb2 100644 --- a/EXILED/Exiled.Events/EventArgs/Item/KeycardInteractingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Item/KeycardInteractingEventArgs.cs @@ -29,7 +29,7 @@ public class KeycardInteractingEventArgs : IPlayerEvent, IDeniableEvent, IDoorEv /// public KeycardInteractingEventArgs(BaseKeycardPickup pickup, Player player, DoorVariant door, bool isAllowed = true) { - Pickup = Pickup.Get(pickup); + KeycardPickup = Pickup.Get(pickup); Player = player; Door = Door.Get(door); IsAllowed = isAllowed; @@ -38,7 +38,10 @@ public KeycardInteractingEventArgs(BaseKeycardPickup pickup, Player player, Door /// /// Gets the item that's interacting with the door. /// - public Pickup Pickup { get; } + public Pickup Pickup => KeycardPickup; + + /// + public KeycardPickup KeycardPickup { get; } /// /// Gets the player who's threw the keycard. diff --git a/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs index f50a87182f..2d28744b27 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/FillingLockerEventArgs.cs @@ -7,11 +7,12 @@ namespace Exiled.Events.EventArgs.Map { + using System; + + using Exiled.API.Features.Lockers; using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Interfaces; - using InventorySystem.Items.Pickups; - using MapGeneration.Distributors; /// @@ -31,7 +32,7 @@ public class FillingLockerEventArgs : IDeniableEvent, IPickupEvent public FillingLockerEventArgs(ItemPickupBase pickupBase, LockerChamber lockerChamber) { Pickup = Pickup.Get(pickupBase); - LockerChamber = lockerChamber; + Chamber = Chamber.Get(lockerChamber); } /// @@ -42,7 +43,18 @@ public FillingLockerEventArgs(ItemPickupBase pickupBase, LockerChamber lockerCha /// /// Gets a value indicating the target locker chamber. /// - public LockerChamber LockerChamber { get; } + [Obsolete("Use Chamber instead.")] + public LockerChamber LockerChamber => Chamber.Base; + + /// + /// Gets a locker which is containing . + /// + public API.Features.Lockers.Locker Locker => Chamber.Locker; + + /// + /// Gets a chamber which is filling. + /// + public Chamber Chamber { get; } /// /// Gets or sets a value indicating whether or not the item can be spawned. diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingLockerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingLockerEventArgs.cs index 2eec0c06b9..46a7dfc63f 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/InteractingLockerEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingLockerEventArgs.cs @@ -7,12 +7,12 @@ namespace Exiled.Events.EventArgs.Player { - using API.Features; + using System; + using API.Features; + using Exiled.API.Features.Lockers; using Interfaces; - using MapGeneration.Distributors; - /// /// Contains all information before a player interacts with a locker. /// @@ -24,41 +24,46 @@ public class InteractingLockerEventArgs : IPlayerEvent, IDeniableEvent /// /// /// - /// - /// - /// /// - /// - /// - /// - /// + /// /// /// /// /// - public InteractingLockerEventArgs(Player player, Locker locker, LockerChamber lockerChamber, byte chamberId, bool isAllowed) + public InteractingLockerEventArgs(Player player, MapGeneration.Distributors.LockerChamber lockerChamber, bool isAllowed) { Player = player; - Locker = locker; - Chamber = lockerChamber; - ChamberId = chamberId; + InteractingChamber = API.Features.Lockers.Chamber.Get(lockerChamber); IsAllowed = isAllowed; } /// /// Gets the instance. /// - public Locker Locker { get; } + [Obsolete("Use InteractingLocker instead.")] + public MapGeneration.Distributors.Locker Locker => InteractingLocker.Base; + + /// + /// Gets the interacting chamber. + /// + [Obsolete("Use InteractingChamber instead.")] + public MapGeneration.Distributors.LockerChamber Chamber => InteractingChamber.Base; + + /// + /// Gets the locker which is containing . + /// + public Locker InteractingLocker => InteractingChamber.Locker; /// /// Gets the interacting chamber. /// - public LockerChamber Chamber { get; } + public Chamber InteractingChamber { get; } /// /// Gets the chamber id. /// - public byte ChamberId { get; } + [Obsolete("Use Chamber::Id instead.")] + public byte ChamberId => InteractingChamber.Id; /// /// Gets or sets a value indicating whether or not the player can interact with the locker. diff --git a/EXILED/Exiled.Events/EventArgs/Player/KillingPlayerEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/KillingPlayerEventArgs.cs index 8655427427..44aba0c4eb 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/KillingPlayerEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/KillingPlayerEventArgs.cs @@ -7,13 +7,15 @@ namespace Exiled.Events.EventArgs.Player { - using Interfaces; + using System; + using Interfaces; using PlayerStatsSystem; /// /// Contains all information before player data to kill player is sent. /// + [Obsolete] public class KillingPlayerEventArgs : IPlayerEvent { /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs new file mode 100644 index 0000000000..cad328ea2e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using API.Features; + + using Interfaces; + + /// + /// Contains all information before a player plays the AudioLog. + /// + public class PlayingAudioLogEventArgs : IPlayerEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public PlayingAudioLogEventArgs(Player player, bool isAllowed = true) + { + Player = player; + IsAllowed = isAllowed; + } + + /// + /// Gets or sets a value indicating whether or not the audio will start. + /// + public bool IsAllowed { get; set; } + + /// + /// Gets the player who started the AudioLog. + /// + public Player Player { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp079/RecontainingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp079/RecontainingEventArgs.cs new file mode 100644 index 0000000000..11d88c3f1e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp079/RecontainingEventArgs.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp079 +{ + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains information before SCP-079 gets recontained. + /// + public class RecontainingEventArgs : IDeniableEvent, IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The instance. + public RecontainingEventArgs(BreakableWindow recontainer) + { + Player = Player.Get(recontainer.LastAttacker.Hub); + IsAutomatic = recontainer.LastAttacker.IsSet; + } + + /// + /// Gets the Player that started the recontainment process.

+ /// Can be null if is true. + ///
+ public Player Player { get; } + + /// + public bool IsAllowed { get; set; } = true; + + /// + /// Gets a value indicating whether or not the recontained has been made automatically or by triggering the proccess. + /// + public bool IsAutomatic { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs new file mode 100644 index 0000000000..a88a5f3a42 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Server +{ + /// + /// Contains all information after a player gets unbanned. + /// + public class UnbannedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + public UnbannedEventArgs(string id, BanHandler.BanType banType) + { + TargetId = id; + BanType = banType; + } + + /// + /// Gets or sets the target player id. + /// + public string TargetId { get; set; } + + /// + /// Gets the ban type. + /// + public BanHandler.BanType BanType { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Server/UnbanningEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/UnbanningEventArgs.cs new file mode 100644 index 0000000000..9822622cd2 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Server/UnbanningEventArgs.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Server +{ + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information before player is unbanned. + /// + public class UnbanningEventArgs : IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public UnbanningEventArgs(string id, BanHandler.BanType banType, bool isAllowed = true) + { + TargetId = id; + BanType = banType; + IsAllowed = isAllowed; + } + + /// + /// Gets or sets the target player id. + /// + public string TargetId { get; set; } + + /// + /// Gets the ban type. + /// + public BanHandler.BanType BanType { get; } + + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Exiled.Events.csproj b/EXILED/Exiled.Events/Exiled.Events.csproj index 421dc0108a..aa7f9ff4bf 100644 --- a/EXILED/Exiled.Events/Exiled.Events.csproj +++ b/EXILED/Exiled.Events/Exiled.Events.csproj @@ -30,6 +30,7 @@ + diff --git a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs index f75f2e91fa..30ad47c9f0 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs @@ -18,7 +18,7 @@ namespace Exiled.Events.Handlers.Internal using Exiled.API.Enums; using Exiled.API.Extensions; - + using Exiled.API.Features.Lockers; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; @@ -47,6 +47,7 @@ public static void OnMapGenerated() { Map.ClearCache(); PrefabHelper.LoadPrefabs(); + Locker.ClearCache(); // TODO: Fix For (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/377) PlayerRoles.RoleAssign.HumanSpawner.Handlers[PlayerRoles.Team.ChaosInsurgency] = new PlayerRoles.RoleAssign.OneRoleHumanSpawner(PlayerRoles.RoleTypeId.ChaosConscript); diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index 016bdf644c..2e62097418 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -7,7 +7,8 @@ namespace Exiled.Events.Handlers { - using Exiled.API.Features.Pickups; + using System; + #pragma warning disable IDE0079 #pragma warning disable IDE0060 #pragma warning disable SA1623 // Property summary documentation should match accessors @@ -208,6 +209,11 @@ public class Player ///
public static Event DroppingNothing { get; set; } = new(); + /// + /// Invoked before playing an AudioLog. + /// + public static Event PlayingAudioLog { get; set; } = new(); + /// /// Invoked before picking up an . /// @@ -511,6 +517,7 @@ public class Player /// /// Invoked before KillPlayer is called. /// + [Obsolete("Use DyingEventArgs")] public static Event KillingPlayer { get; set; } = new(); /// @@ -691,6 +698,12 @@ public class Player /// The instance. public static void OnDroppingNothing(DroppingNothingEventArgs ev) => DroppingNothing.InvokeSafely(ev); + /// + /// Called before a plays an AudioLog. + /// + /// The instance. + public static void OnPlayingAudioLog(PlayingAudioLogEventArgs ev) => PlayingAudioLog.InvokeSafely(ev); + /// /// Called before a picks up an item. /// @@ -965,6 +978,7 @@ public class Player /// Called before KillPlayer is called. /// /// The event handler. + [Obsolete("Use DyingEventArgs")] public static void OnKillPlayer(KillingPlayerEventArgs ev) => KillingPlayer.InvokeSafely(ev); /// diff --git a/EXILED/Exiled.Events/Handlers/Scp079.cs b/EXILED/Exiled.Events/Handlers/Scp079.cs index 85da2da7e5..b272defea3 100644 --- a/EXILED/Exiled.Events/Handlers/Scp079.cs +++ b/EXILED/Exiled.Events/Handlers/Scp079.cs @@ -57,6 +57,11 @@ public static class Scp079 /// public static Event ChangingSpeakerStatus { get; set; } = new(); + /// + /// Invoked before SCP-079 recontainment. + /// + public static Event Recontaining { get; set; } = new(); + /// /// Invoked after SCP-079 recontainment. /// @@ -125,6 +130,12 @@ public static class Scp079 /// The instance. public static void OnChangingSpeakerStatus(ChangingSpeakerStatusEventArgs ev) => ChangingSpeakerStatus.InvokeSafely(ev); + /// + /// Called before SCP-079 is recontained. + /// + /// The instance. + public static void OnRecontaining(RecontainingEventArgs ev) => Recontaining.InvokeSafely(ev); + /// /// Called after SCP-079 is recontained. /// diff --git a/EXILED/Exiled.Events/Handlers/Server.cs b/EXILED/Exiled.Events/Handlers/Server.cs index 75be4d81cd..3f5b71e63e 100644 --- a/EXILED/Exiled.Events/Handlers/Server.cs +++ b/EXILED/Exiled.Events/Handlers/Server.cs @@ -111,6 +111,16 @@ public static class Server ///
public static Event ReloadedPermissions { get; set; } = new(); + /// + /// Invoked before player is being unbanned. + /// + public static Event Unbanning { get; set; } = new(); + + /// + /// Invoked after player is being unbanned. + /// + public static Event Unbanned { get; set; } = new(); + /// /// Called before waiting for players. /// @@ -210,5 +220,17 @@ public static class Server ///
/// The instance. public static void OnSelectingRespawnTeam(SelectingRespawnTeamEventArgs ev) => SelectingRespawnTeam.InvokeSafely(ev); + + /// + /// Called before player is being unbanned. + /// + /// The instance. + public static void OnUnbanning(UnbanningEventArgs ev) => Unbanning.InvokeSafely(ev); + + /// + /// Called after player is being unbanned. + /// + /// The instance. + public static void OnUnbanned(UnbannedEventArgs ev) => Unbanned.InvokeSafely(ev); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Item/KeycardInteracting.cs b/EXILED/Exiled.Events/Patches/Events/Item/KeycardInteracting.cs index 288e5d2125..e26ef525d3 100644 --- a/EXILED/Exiled.Events/Patches/Events/Item/KeycardInteracting.cs +++ b/EXILED/Exiled.Events/Patches/Events/Item/KeycardInteracting.cs @@ -11,10 +11,10 @@ namespace Exiled.Events.Patches.Events.Item using System.Reflection.Emit; using API.Features; + using API.Features.Pickups; using API.Features.Pools; using Exiled.Events; - using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Item; using Footprinting; @@ -23,18 +23,19 @@ namespace Exiled.Events.Patches.Events.Item using Interactables.Interobjects.DoorUtils; - using InventorySystem.Items.Keycards; + using InventorySystem.Items; using UnityEngine; using static HarmonyLib.AccessTools; + using BaseKeycardPickup = InventorySystem.Items.Keycards.KeycardPickup; + /// - /// Patches . + /// Patches and adds implementation. /// Adds the event. /// - [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.KeycardInteracting))] - [HarmonyPatch(typeof(KeycardPickup), nameof(KeycardPickup.ProcessCollision))] + [HarmonyPatch(typeof(BaseKeycardPickup), nameof(BaseKeycardPickup.ProcessCollision))] internal static class KeycardInteracting { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) @@ -46,7 +47,6 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable i.Calls(Method(typeof(DoorPermissions), nameof(DoorPermissions.CheckPermissions)))) + offset; + newInstructions.RemoveRange(index, 14); newInstructions.InsertRange( index, new[] { - new(OpCodes.Stloc_S, havePermissions.LocalIndex), - new(OpCodes.Br_S, skip2), - - // save original return - new CodeInstruction(OpCodes.Ret).MoveLabelsFrom(newInstructions[index + 1]), - new CodeInstruction(OpCodes.Nop).WithLabels(skip2), + // 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), }); - newInstructions.RemoveRange(index + 4, 2); + // 4 new instructions + offset = 4; + index += offset; + + newInstructions.RemoveRange(index, 2); offset = -5; index = newInstructions.FindIndex(i => i.Calls(PropertySetter(typeof(DoorVariant), nameof(DoorVariant.NetworkTargetState)))) + offset; @@ -111,7 +117,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } + + private static bool CheckPermissions(BaseKeycardPickup keycard, DoorPermissions permissions) + { + 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/Player/ActivatingWorkstation.cs b/EXILED/Exiled.Events/Patches/Events/Player/ActivatingWorkstation.cs index 4ee58f8ecd..28edd38031 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ActivatingWorkstation.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ActivatingWorkstation.cs @@ -78,7 +78,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable instruction.Calls(Method(typeof(GameObjectPools.PoolObject), nameof(GameObjectPools.PoolObject.SetupPoolObject)))) + offset; - newInstructions[index].WithLabels(continueLabel1); - newInstructions.InsertRange( index, new[] { - // if (player == null) - // continue - new CodeInstruction(OpCodes.Ldloc_S, player.LocalIndex), - new(OpCodes.Brfalse_S, continueLabel1), - // player.Role = Role.Create(roleBase); new CodeInstruction(OpCodes.Ldloc_S, player.LocalIndex), new(OpCodes.Ldloc_2), @@ -154,22 +140,10 @@ private static IEnumerable Transpiler(IEnumerable i.Calls(Method(typeof(PlayerRoleManager.RoleChanged), nameof(PlayerRoleManager.RoleChanged.Invoke)))) + offset; - newInstructions[index].labels.Add(continueLabel2); - newInstructions.InsertRange( index, - new[] + new CodeInstruction[] { - // if (player == null) - // continue - new CodeInstruction(OpCodes.Ldloc_S, player.LocalIndex), - new(OpCodes.Brfalse_S, continueLabel2), - - // if (changingRoleEventArgs == null) - // continue - new CodeInstruction(OpCodes.Ldloc_S, changingRoleEventArgs.LocalIndex), - new(OpCodes.Brfalse_S, continueLabel2), - // changingRoleEventArgs new(OpCodes.Ldloc_S, changingRoleEventArgs.LocalIndex), @@ -181,11 +155,6 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable +// Copyright (c) Exiled 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; + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using MapGeneration.Spawnables; + + using static HarmonyLib.AccessTools; + + /// + /// Patch the . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.PlayingAudioLog))] + [HarmonyPatch(typeof(AudioLog), nameof(AudioLog.ServerInteract))] + internal static class PlayingAudioLog + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange( + 0, + new CodeInstruction[] + { + // Player player = Player.Get(ReferenceHub); + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // true + new(OpCodes.Ldc_I4_1), + + // PlayingAudioLogEventArgs ev = new(Player, bool) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(PlayingAudioLogEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Player.OnPlayingAudioLog(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnPlayingAudioLog))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(PlayingAudioLogEventArgs), nameof(PlayingAudioLogEventArgs.IsAllowed))), + new(OpCodes.Brfalse, retLabel), + }); + + 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/Player/VoiceChatting.cs b/EXILED/Exiled.Events/Patches/Events/Player/VoiceChatting.cs index 875e099b00..9939938264 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/VoiceChatting.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/VoiceChatting.cs @@ -80,9 +80,8 @@ private static IEnumerable Transpiler(IEnumerable +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp079 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp079; + using Exiled.Events.Handlers; + + using HarmonyLib; + + using PlayerRoles.PlayableScps.Scp079; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Scp079), nameof(Scp079.Recontaining))] + [HarmonyPatch(typeof(Scp079Recontainer), nameof(Scp079Recontainer.Recontain))] + 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[] + { + // RecontainingEventArgs ev = new(this._activatorGlass) + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(Scp079Recontainer), nameof(Scp079Recontainer._activatorGlass))), + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RecontainingEventArgs))[0]), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Scp079.OnRecontaining(ev) + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Call, Method(typeof(Scp079), nameof(Scp079.OnRecontaining))), + + // if (!ev.IsAllowed) return; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(RecontainingEventArgs), nameof(RecontainingEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, returnLabel), + }); + + newInstructions[newInstructions.Count - 1].WithLabels(returnLabel); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Scp096/AddingTarget.cs b/EXILED/Exiled.Events/Patches/Events/Scp096/AddingTarget.cs index 408de36295..b3f1a94bf1 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp096/AddingTarget.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp096/AddingTarget.cs @@ -71,7 +71,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable instruction.StoresField(Field(typeof(ItemPickupBase), nameof(ItemPickupBase.PreviousOwner)))) + jumpOverOffset; - // Remove TryRemove candy logic since we did it earlier from current location - newInstructions.RemoveRange(jumpOverIndex, 6); - int candyKindIdIndex = 4; newInstructions.InsertRange( jumpOverIndex, new[] { - // candyKindID = ev.Candy + // candyKindID = ev.Candy, save locally. new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), new(OpCodes.Callvirt, PropertyGetter(typeof(DroppingScp330EventArgs), nameof(DroppingScp330EventArgs.Candy))), new(OpCodes.Stloc, candyKindIdIndex), - - // candyKindID - new(OpCodes.Ldloc, candyKindIdIndex), }); newInstructions[newInstructions.Count - 1].labels.Add(returnLabel); diff --git a/EXILED/Exiled.Events/Patches/Events/Server/AddingUnitName.cs b/EXILED/Exiled.Events/Patches/Events/Server/AddingUnitName.cs index e8fab3b4aa..960fbd9ca6 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/AddingUnitName.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/AddingUnitName.cs @@ -58,7 +58,7 @@ private static IEnumerable Transpiler(IEnumerable +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Server +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Server; + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches + /// to add and events. + /// + [HarmonyPatch(typeof(BanHandler), nameof(BanHandler.RemoveBan))] + [EventPatch(typeof(Handlers.Server), nameof(Handlers.Server.Unbanning))] + [EventPatch(typeof(Handlers.Server), nameof(Handlers.Server.Unbanned))] + internal class Unban + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(UnbanningEventArgs)); + + Label continueLabel = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // id + new(OpCodes.Ldarg_0), + + // type + new(OpCodes.Ldarg_1), + + // true + new(OpCodes.Ldc_I4_1), + + // UnbanningEventArgs ev = new(string, BanHandler.BanType, true); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(UnbanningEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Server.OnUnbanning(ev); + new(OpCodes.Call, Method(typeof(Handlers.Server), nameof(Handlers.Server.OnUnbanning))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(UnbanningEventArgs), nameof(UnbanningEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, continueLabel), + + new(OpCodes.Ret), + + // id = ev.TargetId; + new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex).WithLabels(continueLabel), + new(OpCodes.Callvirt, PropertyGetter(typeof(UnbanningEventArgs), nameof(UnbanningEventArgs.TargetId))), + new(OpCodes.Starg_S, 0), + }); + + newInstructions.InsertRange(newInstructions.Count - 1, new CodeInstruction[] + { + // id + new(OpCodes.Ldarg_0), + + // type + new(OpCodes.Ldarg_1), + + // UnbannedEventArgs ev2 = new(string, BanHandler.BanType); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(UnbannedEventArgs))[0]), + + // Handlers.Server.OnUnbanned(ev2); + new(OpCodes.Call, Method(typeof(Handlers.Server), nameof(Handlers.Server.OnUnbanned))), + }); + + 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/KillPlayer.cs b/EXILED/Exiled.Events/Patches/Fixes/KillPlayer.cs index cded0dee06..db9f455511 100644 --- a/EXILED/Exiled.Events/Patches/Fixes/KillPlayer.cs +++ b/EXILED/Exiled.Events/Patches/Fixes/KillPlayer.cs @@ -8,6 +8,8 @@ namespace Exiled.Events.Patches.Fixes { #pragma warning disable SA1313 // Parameter names should begin with lower-case letter +#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete using API.Features; using API.Features.DamageHandlers; diff --git a/EXILED/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs b/EXILED/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs new file mode 100644 index 0000000000..fec4aa8693 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs @@ -0,0 +1,32 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System; + using System.Linq; + + using GameCore; + using HarmonyLib; + + /// + /// Fixes the issue where the game was not selecting the scenario with the nearest value. + /// Bug Report + /// + [HarmonyPatch(typeof(AlphaWarheadController), nameof(AlphaWarheadController.Start))] + internal class NWFixDetonationTimer + { + private static void Postfix() + { + AlphaWarheadSyncInfo networkInfo = default; + networkInfo.ScenarioId = Array.IndexOf(AlphaWarheadController.Singleton._startScenarios, AlphaWarheadController.Singleton._startScenarios.OrderBy(d => Math.Abs(d.TimeToDetonate - ConfigFile.ServerConfig.GetInt("warhead_tminus_start_duration", 90))).First()); + + AlphaWarheadController.Singleton.NetworkInfo = networkInfo; + return; + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Generic/DoorList.cs b/EXILED/Exiled.Events/Patches/Generic/DoorList.cs index 4f1ce6e33d..a00a33337b 100644 --- a/EXILED/Exiled.Events/Patches/Generic/DoorList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/DoorList.cs @@ -30,14 +30,48 @@ namespace Exiled.Events.Patches.Generic [HarmonyPatch(typeof(DoorVariant), nameof(DoorVariant.RegisterRooms))] internal class DoorList { - private static void Postfix(DoorVariant __instance) + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { - if (Door.DoorVariantToDoor.ContainsKey(__instance)) + List newInstructions = ListPool.Pool.Get(instructions); + + Label ret = generator.DefineLabel(); + + // if (Rooms != null) + // return; + newInstructions.InsertRange( + 0, + new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(DoorVariant), nameof(DoorVariant.Rooms))), + new(OpCodes.Brtrue_S, ret), + }); + + // DoorList.InitDoor(this); + newInstructions.InsertRange( + newInstructions.Count - 1, + new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Call, Method(typeof(DoorList), nameof(DoorList.InitDoor))), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(ret); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + + private static void InitDoor(DoorVariant doorVariant) + { + if (Door.DoorVariantToDoor.ContainsKey(doorVariant)) return; - List rooms = __instance.Rooms.Select(identifier => Room.RoomIdentifierToRoom[identifier]).ToList(); + List rooms = doorVariant.Rooms.Select(identifier => Room.RoomIdentifierToRoom[identifier]).ToList(); - Door door = Door.Create(__instance, rooms); + Door door = Door.Create(doorVariant, rooms); foreach (Room room in rooms) { diff --git a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs index 26b0732abe..7dadd8817a 100644 --- a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs @@ -12,7 +12,7 @@ namespace Exiled.Events.Patches.Generic using API.Features; using API.Features.Pools; - + using Exiled.API.Features.Lockers; using HarmonyLib; using MapGeneration.Distributors; @@ -20,23 +20,23 @@ namespace Exiled.Events.Patches.Generic using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// - [HarmonyPatch(typeof(Locker), nameof(Locker.Start))] + [HarmonyPatch(typeof(MapGeneration.Distributors.Locker), nameof(MapGeneration.Distributors.Locker.Start))] internal class LockerList { private static IEnumerable Transpiler(IEnumerable codeInstructions) { List newInstructions = ListPool.Pool.Get(codeInstructions); - // Map.LockersValue.Add(this); + // new Locker(this) newInstructions.InsertRange( 0, new CodeInstruction[] { - new(OpCodes.Ldsfld, Field(typeof(Map), nameof(Map.LockersValue))), new(OpCodes.Ldarg_0), - new(OpCodes.Callvirt, Method(typeof(List), nameof(List.Add), new[] { typeof(Locker) })), + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(API.Features.Lockers.Locker))[0]), + new(OpCodes.Pop), }); for (int z = 0; z < newInstructions.Count; z++) diff --git a/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs b/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs index 5698191921..f9a9129493 100644 --- a/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs +++ b/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs @@ -44,7 +44,7 @@ private static IEnumerable Transpiler(IEnumerable +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic.Scp079API +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + + using HarmonyLib; + + using PlayerRoles.PlayableScps.Scp079; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the support. + /// + [HarmonyPatch(typeof(Scp079Recontainer), nameof(Scp079Recontainer.OnServerRoleChanged))] + internal class Scp079Recontain + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label ret = generator.DefineLabel(); + + newInstructions.InsertRange( + 0, + new[] + { + // if (!Events.Instance.Config.ShouldScp079RecontainedWhenNoScps) + // return; + new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(Exiled.Events.Events), nameof(Exiled.Events.Events.Instance))), + new(OpCodes.Callvirt, PropertyGetter(typeof(Exiled.Events.Events), nameof(Exiled.Events.Events.Config))), + new(OpCodes.Callvirt, PropertyGetter(typeof(Exiled.Events.Config), nameof(Exiled.Events.Config.RecontainScp079IfNoScpsLeft))), + new(OpCodes.Brfalse_S, ret), + }); + + 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/Generic/StaminaRegenArmor.cs b/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs new file mode 100644 index 0000000000..e4b83f10e3 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs @@ -0,0 +1,30 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using Exiled.API.Features; + using Exiled.API.Features.Items; +#pragma warning disable SA1313 + + using HarmonyLib; + using InventorySystem.Items.Armor; + + /// + /// Patches . + /// Implements . + /// + [HarmonyPatch(typeof(BodyArmor), nameof(BodyArmor.StaminaRegenMultiplier), MethodType.Getter)] + internal class StaminaRegenArmor + { + private static void Postfix(BodyArmor __instance, ref float __result) + { + if(Item.Get(__instance) is Armor armor) + __result *= armor.StaminaRegenMultiplier; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Loader/Exiled.Loader.csproj b/EXILED/Exiled.Loader/Exiled.Loader.csproj index 4a48187e26..cb3a04e397 100644 --- a/EXILED/Exiled.Loader/Exiled.Loader.csproj +++ b/EXILED/Exiled.Loader/Exiled.Loader.csproj @@ -17,6 +17,7 @@ + diff --git a/EXILED/Exiled.Loader/LinuxPermission.cs b/EXILED/Exiled.Loader/LinuxPermission.cs index 1c47e340bb..d5bf1da12d 100644 --- a/EXILED/Exiled.Loader/LinuxPermission.cs +++ b/EXILED/Exiled.Loader/LinuxPermission.cs @@ -12,13 +12,13 @@ namespace Exiled.Loader /// /// A set of extensions to easily interact with Linux/Unix environment. /// - internal static class LinuxPermission + public static class LinuxPermission { /// /// Sets rw and execution permissions given a file, for the current user and group. /// /// The path of the file. - internal static void SetFileUserAndGroupReadWriteExecutePermissions(string path) + public static void SetFileUserAndGroupReadWriteExecutePermissions(string path) { UnixFileSystemInfo.GetFileSystemEntry(path).FileAccessPermissions |= FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupReadWriteExecute; } From e74818adde45c579b32b91908db3fdb24743528e Mon Sep 17 00:00:00 2001 From: Jesus QC <69375249+Jesus-QC@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:56:36 +0200 Subject: [PATCH 2/4] Update README.md --- .github/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index b901da96d4..3c45a2b457 100644 --- a/.github/README.md +++ b/.github/README.md @@ -19,7 +19,7 @@

- EXILED Development + EXILED Development

From 65d9a03721faa6818f300c3cbc219de9e93af968 Mon Sep 17 00:00:00 2001 From: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:22:47 -0400 Subject: [PATCH 3/4] Update push_nuget.yml --- .github/workflows/push_nuget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index 98de8efbf3..675dd31d24 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -46,7 +46,7 @@ jobs: - name: Push NuGet shell: pwsh - run: nuget push ${{ env.PackageFile }} -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json + run: nuget push ${{ env.PackageFile }} -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json --skip-duplicate - name: Push generated package to GitHub registry run: dotnet nuget push ${{ env.PackageFile }} -k ${{ secrets.GITHUB_TOKEN }} -s https://nuget.pkg.github.com/ExMod-Team/index.json --skip-duplicate From 6e2f1fd8b1d987a88bf9d91d700f69c48d290312 Mon Sep 17 00:00:00 2001 From: Misaka-ZeroTwo <45165615+Misaka-ZeroTwo@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:28:11 -0400 Subject: [PATCH 4/4] Update push_nuget.yml --- .github/workflows/push_nuget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index 675dd31d24..12c4e893ff 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -46,7 +46,7 @@ jobs: - name: Push NuGet shell: pwsh - run: nuget push ${{ env.PackageFile }} -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json --skip-duplicate + run: nuget push ${{ env.PackageFile }} -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json -SkipDuplicate - name: Push generated package to GitHub registry run: dotnet nuget push ${{ env.PackageFile }} -k ${{ secrets.GITHUB_TOKEN }} -s https://nuget.pkg.github.com/ExMod-Team/index.json --skip-duplicate