From 6585b640c66b4b16f7d174e1c486f0dd5f692290 Mon Sep 17 00:00:00 2001 From: Mike <146554836+MikeSus1@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:15:04 +0100 Subject: [PATCH 01/33] refractor!: rename blindness effectype (#317) * Fix EffectType * fix: ref fix build * Blinded -> Blurred replace all blinded by blurred * Docs docs blindness effect --- EXILED/Exiled.API/Enums/EffectType.cs | 6 +++--- .../Exiled.API/Extensions/EffectTypeExtension.cs | 4 ++-- EXILED/Exiled.API/Features/Items/FlashGrenade.cs | 14 +++++++------- .../Features/Pickups/FlashGrenadePickup.cs | 10 +++++----- .../Pickups/Projectiles/FlashbangProjectile.cs | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/EXILED/Exiled.API/Enums/EffectType.cs b/EXILED/Exiled.API/Enums/EffectType.cs index d2f0a1394b..dc79e7c481 100644 --- a/EXILED/Exiled.API/Enums/EffectType.cs +++ b/EXILED/Exiled.API/Enums/EffectType.cs @@ -44,9 +44,9 @@ public enum EffectType Bleeding, /// - /// Blurs the player's screen. + /// Make the player screen darker. /// - Blinded, + Blindness, /// /// Increases damage the player receives. Does not apply any standalone damage. @@ -256,7 +256,7 @@ public enum EffectType PitDeath, /// - /// . + /// Blurs the player's screen. /// Blurred, diff --git a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs index 088438e085..69677e71c7 100644 --- a/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs +++ b/EXILED/Exiled.API/Extensions/EffectTypeExtension.cs @@ -34,7 +34,7 @@ public static class EffectTypeExtension { EffectType.AmnesiaVision, typeof(AmnesiaVision) }, { EffectType.Asphyxiated, typeof(Asphyxiated) }, { EffectType.Bleeding, typeof(Bleeding) }, - { EffectType.Blinded, typeof(Blindness) }, + { EffectType.Blindness, typeof(Blindness) }, { EffectType.BodyshotReduction, typeof(BodyshotReduction) }, { EffectType.Burned, typeof(Burned) }, { EffectType.CardiacArrest, typeof(CardiacArrest) }, @@ -194,7 +194,7 @@ or EffectType.Corroding or EffectType.Decontaminating or EffectType.Hemorrhage o /// Whether the effect is a negative effect. /// public static bool IsNegative(this EffectType effect) => IsHarmful(effect) || effect is EffectType.AmnesiaItems - or EffectType.AmnesiaVision or EffectType.Blinded or EffectType.Burned or EffectType.Concussed or EffectType.Deafened + or EffectType.AmnesiaVision or EffectType.Blindness or EffectType.Burned or EffectType.Concussed or EffectType.Deafened or EffectType.Disabled or EffectType.Ensnared or EffectType.Exhausted or EffectType.Flashed or EffectType.SinkHole or EffectType.Stained or EffectType.InsufficientLighting or EffectType.SoundtrackMute or EffectType.Scanned or EffectType.Slowness; diff --git a/EXILED/Exiled.API/Features/Items/FlashGrenade.cs b/EXILED/Exiled.API/Features/Items/FlashGrenade.cs index 867d11b3ad..88e9129fb0 100644 --- a/EXILED/Exiled.API/Features/Items/FlashGrenade.cs +++ b/EXILED/Exiled.API/Features/Items/FlashGrenade.cs @@ -59,12 +59,12 @@ public float MinimalDurationEffect } /// - /// Gets or sets the additional duration of the effect. + /// Gets or sets the additional duration of the effect. /// - public float AdditionalBlindedEffect + public float AdditionalBlurredEffect { - get => Projectile.AdditionalBlindedEffect; - set => Projectile.AdditionalBlindedEffect = value; + get => Projectile.AdditionalBlurredEffect; + set => Projectile.AdditionalBlurredEffect = value; } /// @@ -105,7 +105,7 @@ public FlashbangProjectile SpawnActive(Vector3 position, Player owner = null) grenade.Base.gameObject.SetActive(true); grenade.MinimalDurationEffect = MinimalDurationEffect; - grenade.AdditionalBlindedEffect = AdditionalBlindedEffect; + grenade.AdditionalBlurredEffect = AdditionalBlurredEffect; grenade.SurfaceDistanceIntensifier = SurfaceDistanceIntensifier; grenade.FuseTime = FuseTime; @@ -125,7 +125,7 @@ public FlashbangProjectile SpawnActive(Vector3 position, Player owner = null) public override Item Clone() => new FlashGrenade() { MinimalDurationEffect = MinimalDurationEffect, - AdditionalBlindedEffect = AdditionalBlindedEffect, + AdditionalBlurredEffect = AdditionalBlurredEffect, SurfaceDistanceIntensifier = SurfaceDistanceIntensifier, FuseTime = FuseTime, Repickable = Repickable, @@ -145,7 +145,7 @@ internal override void ReadPickupInfoBefore(Pickup pickup) if (pickup is FlashGrenadePickup flashGrenadePickup) { MinimalDurationEffect = flashGrenadePickup.MinimalDurationEffect; - AdditionalBlindedEffect = flashGrenadePickup.AdditionalBlindedEffect; + AdditionalBlurredEffect = flashGrenadePickup.AdditionalBlurredEffect; SurfaceDistanceIntensifier = flashGrenadePickup.SurfaceDistanceIntensifier; FuseTime = flashGrenadePickup.FuseTime; } diff --git a/EXILED/Exiled.API/Features/Pickups/FlashGrenadePickup.cs b/EXILED/Exiled.API/Features/Pickups/FlashGrenadePickup.cs index 3e4c979b50..3cdb59898e 100644 --- a/EXILED/Exiled.API/Features/Pickups/FlashGrenadePickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/FlashGrenadePickup.cs @@ -42,9 +42,9 @@ internal FlashGrenadePickup() public float MinimalDurationEffect { get; set; } /// - /// Gets or sets the additional duration of the effect. + /// Gets or sets the additional duration of the effect. /// - public float AdditionalBlindedEffect { get; set; } + public float AdditionalBlurredEffect { get; set; } /// /// Gets or sets the how mush the flash grenade going to be intensified when explode at . @@ -58,7 +58,7 @@ internal override void ReadItemInfo(Item item) if (item is FlashGrenade flashGrenadeitem) { MinimalDurationEffect = flashGrenadeitem.MinimalDurationEffect; - AdditionalBlindedEffect = flashGrenadeitem.AdditionalBlindedEffect; + AdditionalBlurredEffect = flashGrenadeitem.AdditionalBlurredEffect; SurfaceDistanceIntensifier = flashGrenadeitem.SurfaceDistanceIntensifier; FuseTime = flashGrenadeitem.FuseTime; } @@ -70,7 +70,7 @@ internal override void WriteProjectileInfo(Projectile projectile) if (projectile is FlashbangProjectile flashbangProjectile) { flashbangProjectile.MinimalDurationEffect = MinimalDurationEffect; - flashbangProjectile.AdditionalBlindedEffect = AdditionalBlindedEffect; + flashbangProjectile.AdditionalBlurredEffect = AdditionalBlurredEffect; flashbangProjectile.SurfaceDistanceIntensifier = SurfaceDistanceIntensifier; flashbangProjectile.FuseTime = FuseTime; } @@ -83,7 +83,7 @@ protected override void InitializeProperties(ItemBase itemBase) if (itemBase is ThrowableItem throwable && throwable.Projectile is FlashbangGrenade flashGrenade) { MinimalDurationEffect = flashGrenade._minimalEffectDuration; - AdditionalBlindedEffect = flashGrenade._additionalBlurDuration; + AdditionalBlurredEffect = flashGrenade._additionalBlurDuration; SurfaceDistanceIntensifier = flashGrenade._surfaceZoneDistanceIntensifier; } } diff --git a/EXILED/Exiled.API/Features/Pickups/Projectiles/FlashbangProjectile.cs b/EXILED/Exiled.API/Features/Pickups/Projectiles/FlashbangProjectile.cs index 8c289c2840..8d30856743 100644 --- a/EXILED/Exiled.API/Features/Pickups/Projectiles/FlashbangProjectile.cs +++ b/EXILED/Exiled.API/Features/Pickups/Projectiles/FlashbangProjectile.cs @@ -51,9 +51,9 @@ public float MinimalDurationEffect } /// - /// Gets or sets the additional duration of the effect. + /// Gets or sets the additional duration of the effect. /// - public float AdditionalBlindedEffect + public float AdditionalBlurredEffect { get => Base._additionalBlurDuration; set => Base._additionalBlurDuration = value; From d728d1b5737005d10c5bc1e8d96f454ce40131af Mon Sep 17 00:00:00 2001 From: TiBarification Date: Mon, 16 Feb 2026 16:15:23 +0200 Subject: [PATCH 02/33] refactor!: ahp granting (#415) * Return AhpStat.AhpProcess instance after adding AHp * return --- EXILED/Exiled.API/Features/Player.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index d91e703c01..bfac75fbe0 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3705,9 +3705,10 @@ public void ChangeEffectIntensity(string effectName, byte intensity, float durat /// Percent of incoming damage absorbed by this stat. /// The number of seconds to delay the start of the decay. /// Whether the process is removed when the value hits 0. - public void AddAhp(float amount, float limit = 75f, float decay = 1.2f, float efficacy = 0.7f, float sustain = 0f, bool persistant = false) + /// The instance.. + public AhpStat.AhpProcess AddAhp(float amount, float limit = 75f, float decay = 1.2f, float efficacy = 0.7f, float sustain = 0f, bool persistant = false) { - ReferenceHub.playerStats.GetModule() + return ReferenceHub.playerStats.GetModule() .ServerAddProcess(amount, limit, decay, efficacy, sustain, persistant); } From 65a30336d7370b3428b83e20b2518a2b1a1239d1 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:15:43 +0100 Subject: [PATCH 03/33] feat: Bump Lib.Harmony from 2.2.2 to 2.4.2 in /EXILED (#755) --- EXILED/EXILED.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index f5197ff43b..f2b9761486 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -19,7 +19,7 @@ false - 2.2.2 + 2.4.2 1.1.118 2.0.2 From f313ff933b5c53210309cdd3946d181587d6afff Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:16:46 +0300 Subject: [PATCH 04/33] feat: Speaker Api (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Speaker Toy Api * . * chore: trigger CI * added missing doc * Change default stream parameter to false in PlayWav * Improve XML documentation for Channel property Updated XML documentation for the Channel property to include a reference to Channels. * Update WavStreamSource.cs Update PreloadedPcmSource.cs Update Speaker.cs Create WavUtility.cs Update WavStreamSource.cs Update PreloadedPcmSource.cs Change default stream parameter to false in PlayWav Update PlayWav method to default stream to true Change TargetPlayers from List to HashSet * Update Speaker.cs * Update Speaker.cs * Update performance * Update Speaker.cs Update PreloadedPcmSource.cs Added another constructor for public usages Update PreloadedPcmSource.cs * Implemented audio seeking, event system, and precision timing - Introduced a full Event system to the Speaker class (Started, Finished, Stopped, Paused, Resumed). - Implemented `Seek()` functionality and added `CurrentTime` / `TotalDuration` properties. - Utilized `double` precision for all time related calculations to ensure accuracy. - Updated `WavStreamSource` and `PreloadedPcmSource` to support the new seeking logic. * Update Speaker.cs * Update WavUtility.cs * Update Speaker.cs * Update Speaker.cs * Update Speaker.cs * Update Speaker.cs * Added OnPlaybackLooped event * Update Speaker.cs Refactor: Optimize WavStreamSource with ArrayPool & dynamic buffering Feat: Add Pitch (it can be reversed if you wish) Update EXILED.props * Update to c# lang version 14 * Update Speaker.cs * Enum 4 byte to 1 byte * -Replaced 'File.ReadAllBytes' with 'ArrayPool.Shared' -Optimized WAV header skipping (string comparisons to direct uint32 hex checks) * Refactor * Initialize Channel property with ReliableOrdered2 * Refactor Speaker class properties * Update Speaker.cs * Update Speaker.cs * ağhhh * renamed method * Added Target Player Play Mode * . * added lasttrack * perf * Gc halleder onları * ö * added lasttrack to finishedTrack --- EXILED/EXILED.props | 2 +- EXILED/Exiled.API/Enums/SpeakerPlayMode.cs | 35 ++ .../Features/Audio/PreloadedPcmSource.cs | 114 +++++ .../Features/Audio/WavStreamSource.cs | 144 ++++++ .../Exiled.API/Features/Audio/WavUtility.cs | 116 +++++ .../Features/Core/Generic/EnumClass.cs | 6 +- .../Core/Generic/UnmanagedEnumClass.cs | 6 +- EXILED/Exiled.API/Features/Toys/Speaker.cs | 431 +++++++++++++++++- EXILED/Exiled.API/Interfaces/IPcmSource.cs | 52 +++ 9 files changed, 894 insertions(+), 12 deletions(-) create mode 100644 EXILED/Exiled.API/Enums/SpeakerPlayMode.cs create mode 100644 EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs create mode 100644 EXILED/Exiled.API/Features/Audio/WavStreamSource.cs create mode 100644 EXILED/Exiled.API/Features/Audio/WavUtility.cs create mode 100644 EXILED/Exiled.API/Interfaces/IPcmSource.cs diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index f2b9761486..af916a48b3 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -7,7 +7,7 @@ net48 - 13.0 + 14.0 x64 false $(MSBuildThisFileDirectory)\bin\$(Configuration)\ diff --git a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs new file mode 100644 index 0000000000..9bafa513e1 --- /dev/null +++ b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// Specifies the available modes for playing audio through a speaker. + /// + public enum SpeakerPlayMode : byte + { + /// + /// Play audio globally to all players. + /// + Global = 0, + + /// + /// Play audio to a specific player. + /// + Player = 1, + + /// + /// Play audio to a specific list of players. + /// + PlayerList = 2, + + /// + /// Play audio to players matching a predicate. + /// + Predicate = 3, + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs new file mode 100644 index 0000000000..7be9d09a30 --- /dev/null +++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs @@ -0,0 +1,114 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Audio +{ + using System; + + using Exiled.API.Interfaces; + + using VoiceChat; + + /// + /// Represents a preloaded PCM audio source. + /// + public sealed class PreloadedPcmSource : IPcmSource + { + /// + /// The PCM data buffer. + /// + private readonly float[] data; + + /// + /// The current read position in the data buffer. + /// + private int pos; + + /// + /// Initializes a new instance of the class. + /// + /// The path to the audio file. + public PreloadedPcmSource(string path) + { + data = WavUtility.WavToPcm(path); + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw PCM float array. + public PreloadedPcmSource(float[] pcmData) + { + data = pcmData; + } + + /// + /// Gets a value indicating whether the end of the PCM data buffer has been reached. + /// + public bool Ended => pos >= data.Length; + + /// + /// Gets the total duration of the audio in seconds. + /// + public double TotalDuration => (double)data.Length / VoiceChatSettings.SampleRate; + + /// + /// Gets or sets the current playback position in seconds. + /// + public double CurrentTime + { + get => (double)pos / VoiceChatSettings.SampleRate; + set => Seek(value); + } + + /// + /// Reads a sequence of PCM samples from the preloaded buffer into the specified array. + /// + /// The destination array to copy the samples into. + /// The zero-based index in at which to begin storing the data. + /// The maximum number of samples to read. + /// The number of samples read into . + public int Read(float[] buffer, int offset, int count) + { + int read = Math.Min(count, data.Length - pos); + Array.Copy(data, pos, buffer, offset, read); + pos += read; + + return read; + } + + /// + /// Seeks to the specified position in seconds. + /// + /// The target position in seconds. + public void Seek(double seconds) + { + long targetIndex = (long)(seconds * VoiceChatSettings.SampleRate); + + if (targetIndex < 0) + targetIndex = 0; + + if (targetIndex > data.Length) + targetIndex = data.Length; + + pos = (int)targetIndex; + } + + /// + /// Resets the read position to the beginning of the PCM data buffer. + /// + public void Reset() + { + pos = 0; + } + + /// + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs new file mode 100644 index 0000000000..d910093f1b --- /dev/null +++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs @@ -0,0 +1,144 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Audio +{ + using System; + using System.Buffers; + using System.IO; + using System.Runtime.InteropServices; + + using Exiled.API.Interfaces; + + using VoiceChat; + + /// + /// Provides a PCM audio source from a WAV file stream. + /// + public sealed class WavStreamSource : IPcmSource + { + private const float Divide = 1f / 32768f; + + private readonly long endPosition; + private readonly long startPosition; + private readonly FileStream stream; + + private byte[] internalBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The path to the audio file. + public WavStreamSource(string path) + { + stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 64 * 1024, FileOptions.SequentialScan); + WavUtility.SkipHeader(stream); + startPosition = stream.Position; + endPosition = stream.Length; + internalBuffer = ArrayPool.Shared.Rent(VoiceChatSettings.PacketSizePerChannel * 2); + } + + /// + /// Gets the total duration of the audio in seconds. + /// + public double TotalDuration => (endPosition - startPosition) / 2.0 / VoiceChatSettings.SampleRate; + + /// + /// Gets or sets the current playback position in seconds. + /// + public double CurrentTime + { + get => (stream.Position - startPosition) / 2.0 / VoiceChatSettings.SampleRate; + set => Seek(value); + } + + /// + /// Gets a value indicating whether the end of the stream has been reached. + /// + public bool Ended => stream.Position >= endPosition; + + /// + /// Reads PCM data from the stream into the specified buffer. + /// + /// The buffer to fill with PCM data. + /// The offset in the buffer at which to begin writing. + /// The maximum number of samples to read. + /// The number of samples read. + public int Read(float[] buffer, int offset, int count) + { + int bytesNeeded = count * 2; + + if (internalBuffer.Length < bytesNeeded) + { + ArrayPool.Shared.Return(internalBuffer); + internalBuffer = ArrayPool.Shared.Rent(bytesNeeded); + } + + int bytesRead = stream.Read(internalBuffer, 0, bytesNeeded); + + if (bytesRead == 0) + return 0; + + if (bytesRead % 2 != 0) + bytesRead--; + + Span byteSpan = internalBuffer.AsSpan(0, bytesRead); + Span shortSpan = MemoryMarshal.Cast(byteSpan); + + int samplesInDestination = buffer.Length - offset; + int samplesToWrite = Math.Min(shortSpan.Length, samplesInDestination); + + for (int i = 0; i < samplesToWrite; i++) + buffer[offset + i] = shortSpan[i] * Divide; + + return samplesToWrite; + } + + /// + /// Seeks to the specified position in the stream. + /// + /// The position in seconds to seek to. + public void Seek(double seconds) + { + long targetSample = (long)(seconds * VoiceChatSettings.SampleRate); + long targetByte = targetSample * 2; + + long newPos = startPosition + targetByte; + if (newPos > endPosition) + newPos = endPosition; + + if (newPos < startPosition) + newPos = startPosition; + + if (newPos % 2 != 0) + newPos--; + + stream.Position = newPos; + } + + /// + /// Resets the stream position to the start. + /// + public void Reset() + { + stream.Position = startPosition; + } + + /// + /// Releases all resources used by the . + /// + public void Dispose() + { + stream?.Dispose(); + if (internalBuffer != null) + { + ArrayPool.Shared.Return(internalBuffer); + internalBuffer = null; + } + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs new file mode 100644 index 0000000000..c1b1bc3f73 --- /dev/null +++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs @@ -0,0 +1,116 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Audio +{ + using System; + using System.Buffers; + using System.Buffers.Binary; + using System.IO; + using System.Runtime.InteropServices; + + using VoiceChat; + + /// + /// Provides utility methods for working with WAV audio files. + /// + public static class WavUtility + { + private const float Divide = 1f / 32768f; + + /// + /// Converts a WAV file at the specified path to a PCM float array. + /// + /// The file path of the WAV file to convert. + /// An array of floats representing the PCM data. + public static float[] WavToPcm(string path) + { + using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.Read); + int length = (int)fs.Length; + + byte[] rentedBuffer = ArrayPool.Shared.Rent(length); + + try + { + int bytesRead = fs.Read(rentedBuffer, 0, length); + + using MemoryStream ms = new(rentedBuffer, 0, bytesRead); + + SkipHeader(ms); + + int headerOffset = (int)ms.Position; + int dataLength = bytesRead - headerOffset; + + Span audioDataSpan = rentedBuffer.AsSpan(headerOffset, dataLength); + Span samples = MemoryMarshal.Cast(audioDataSpan); + + float[] pcm = new float[samples.Length]; + + for (int i = 0; i < samples.Length; i++) + pcm[i] = samples[i] * Divide; + + return pcm; + } + finally + { + ArrayPool.Shared.Return(rentedBuffer); + } + } + + /// + /// Skips the WAV file header and validates that the format is PCM16 mono with the specified sample rate. + /// + /// The to read from. + public static void SkipHeader(Stream stream) + { + Span headerBuffer = stackalloc byte[12]; + stream.Read(headerBuffer); + + Span chunkHeader = stackalloc byte[8]; + while (true) + { + int read = stream.Read(chunkHeader); + if (read < 8) + break; + + uint chunkId = BinaryPrimitives.ReadUInt32LittleEndian(chunkHeader[..4]); + int chunkSize = BinaryPrimitives.ReadInt32LittleEndian(chunkHeader.Slice(4, 4)); + + // 'fmt ' chunk + if (chunkId == 0x20746D66) + { + Span fmtData = stackalloc byte[16]; + stream.Read(fmtData); + + short format = BinaryPrimitives.ReadInt16LittleEndian(fmtData[..2]); + short channels = BinaryPrimitives.ReadInt16LittleEndian(fmtData.Slice(2, 2)); + int rate = BinaryPrimitives.ReadInt32LittleEndian(fmtData.Slice(4, 4)); + short bits = BinaryPrimitives.ReadInt16LittleEndian(fmtData.Slice(14, 2)); + + if (format != 1 || channels != 1 || rate != VoiceChatSettings.SampleRate || bits != 16) + throw new InvalidDataException($"Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz."); + + if (chunkSize > 16) + stream.Seek(chunkSize - 16, SeekOrigin.Current); + } + + // 'data' chunk + else if (chunkId == 0x61746164) + { + return; + } + else + { + stream.Seek(chunkSize, SeekOrigin.Current); + } + + if (stream.Position >= stream.Length) + throw new InvalidDataException("WAV file does not contain a 'data' chunk."); + } + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs index 5f883d0bcf..0f3077d874 100644 --- a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs +++ b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs @@ -67,10 +67,10 @@ public string Name .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public) .Where(t => t.FieldType == typeof(TObject)); - foreach (FieldInfo field in fields) + foreach (FieldInfo @field in fields) { - TObject instance = (TObject)field.GetValue(null); - instance.name = field.Name; + TObject instance = (TObject)@field.GetValue(null); + instance.name = @field.Name; } isDefined = true; diff --git a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs index 7928f38783..ce82315efa 100644 --- a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs +++ b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs @@ -67,10 +67,10 @@ public string Name .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public) .Where(t => t.FieldType == typeof(TObject)); - foreach (FieldInfo field in fields) + foreach (FieldInfo @field in fields) { - TObject instance = (TObject)field.GetValue(null); - instance.name = field.Name; + TObject instance = (TObject)@field.GetValue(null); + instance.name = @field.Name; } isDefined = true; diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs index 8b0af6bd79..4478c1143b 100644 --- a/EXILED/Exiled.API/Features/Toys/Speaker.cs +++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs @@ -7,20 +7,53 @@ namespace Exiled.API.Features.Toys { + using System; using System.Collections.Generic; + using System.IO; using AdminToys; + using Enums; - using Exiled.API.Interfaces; + + using Exiled.API.Features.Audio; + + using Interfaces; + + using MEC; + + using Mirror; + using UnityEngine; + + using VoiceChat; + using VoiceChat.Codec; + using VoiceChat.Codec.Enums; using VoiceChat.Networking; - using VoiceChat.Playbacks; + + using Object = UnityEngine.Object; /// /// A wrapper class for . /// public class Speaker : AdminToy, IWrapper { + private const int FrameSize = VoiceChatSettings.PacketSizePerChannel; + private const float FrameTime = (float)FrameSize / VoiceChatSettings.SampleRate; + + private float[] frame; + private byte[] encoded; + private float[] resampleBuffer; + + private double resampleTime; + private int resampleBufferFilled; + + private IPcmSource source; + private OpusEncoder encoder; + private CoroutineHandle playBackRoutine; + + private bool isPitchDefault = true; + private bool isPlayBackInitialized = false; + /// /// Initializes a new instance of the class. /// @@ -28,6 +61,37 @@ public class Speaker : AdminToy, IWrapper internal Speaker(SpeakerToy speakerToy) : base(speakerToy, AdminToyType.Speaker) => Base = speakerToy; + /// + /// Invoked when the audio playback starts. + /// + public event Action OnPlaybackStarted; + + /// + /// Invoked when the audio playback is paused. + /// + public event Action OnPlaybackPaused; + + /// + /// Invoked when the audio playback is resumed from a paused state. + /// + public event Action OnPlaybackResumed; + + /// + /// Invoked when the audio playback loops back to the beginning. + /// + public event Action OnPlaybackLooped; + + /// + /// Invoked when the audio track finishes playing. + /// If looping is enabled, this triggers every time the track finished. + /// + public event Action OnPlaybackFinished; + + /// + /// Invoked when the audio playback stops completely (either manually or end of file). + /// + public event Action OnPlaybackStopped; + /// /// Gets the prefab. /// @@ -38,6 +102,123 @@ internal Speaker(SpeakerToy speakerToy) /// public SpeakerToy Base { get; } + /// + /// Gets or sets the network channel used for sending audio packets from this speaker . + /// + public int Channel { get; set; } = Channels.ReliableOrdered2; + + /// + /// Gets or sets a value indicating whether the audio playback should loop when it reaches the end. + /// + public bool Loop { get; set; } + + /// + /// Gets or sets a value indicating whether the speaker should be destroyed after playback finishes. + /// + public bool DestroyAfter { get; set; } + + /// + /// Gets or sets the play mode for this speaker, determining how audio is sent to players. + /// + public SpeakerPlayMode PlayMode { get; set; } + + /// + /// Gets or sets the target player who will hear the audio played by this speaker when is set to . + /// + public Player TargetPlayer { get; set; } + + /// + /// Gets or sets the list of target players who will hear the audio played by this speaker when is set to . + /// + public HashSet TargetPlayers { get; set; } + + /// + /// Gets or sets the predicate used to determine which players will hear the audio when is set to . + /// The predicate should return true for players who should receive the audio. + /// + public Func Predicate { get; set; } + + /// + /// Gets a value indicating whether gets is a sound playing on this speaker or not. + /// + public bool IsPlaying => playBackRoutine.IsRunning && !IsPaused; + + /// + /// Gets or sets a value indicating whether the playback is paused. + /// + /// + /// A where true means the playback is paused; false means it is not paused. + /// + public bool IsPaused + { + get => playBackRoutine.IsAliveAndPaused; + set + { + if (!playBackRoutine.IsRunning) + return; + + if (playBackRoutine.IsAliveAndPaused == value) + return; + + playBackRoutine.IsAliveAndPaused = value; + if (value) + OnPlaybackPaused?.Invoke(); + else + OnPlaybackResumed?.Invoke(); + } + } + + /// + /// Gets or sets the current playback time in seconds. + /// Returns 0 if not playing. + /// + public double CurrentTime + { + get => source?.CurrentTime ?? 0.0; + set + { + if (source != null) + { + source.CurrentTime = value; + resampleTime = 0.0; + resampleBufferFilled = 0; + } + } + } + + /// + /// Gets the total duration of the current track in seconds. + /// Returns 0 if not playing. + /// + public double TotalDuration => source?.TotalDuration ?? 0.0; + + /// + /// Gets the path to the last audio file played on this speaker. + /// + public string LastTrack { get; private set; } + + /// + /// Gets or sets the playback pitch. + /// + /// + /// A representing the pitch level of the audio source, + /// where 1.0 is normal pitch, less than 1.0 is lower pitch (slower), and greater than 1.0 is higher pitch (faster). + /// + public float Pitch + { + get; + set + { + field = Mathf.Max(0.1f, Mathf.Abs(value)); + isPitchDefault = Mathf.Abs(field - 1.0f) < 0.0001f; + if (isPitchDefault) + { + resampleTime = 0.0; + resampleBufferFilled = 0; + } + } + } + /// /// Gets or sets the volume of the audio source. /// @@ -109,7 +290,7 @@ public byte ControllerId /// The new . public static Speaker Create(Vector3? position, Vector3? rotation, Vector3? scale, bool spawn) { - Speaker speaker = new(UnityEngine.Object.Instantiate(Prefab)) + Speaker speaker = new(Object.Instantiate(Prefab)) { Position = position ?? Vector3.zero, Rotation = Quaternion.Euler(rotation ?? Vector3.zero), @@ -138,7 +319,7 @@ public static Speaker Create(Transform transform, bool spawn, bool worldPosition Scale = transform.localScale.normalized, }; - if(spawn) + if (spawn) speaker.Spawn(); return speaker; @@ -162,5 +343,245 @@ public static void Play(AudioMessage message, IEnumerable targets = null /// The length of the samples array. /// Targets who will hear the audio. If null, audio will be sent to all players. public void Play(byte[] samples, int? length = null, IEnumerable targets = null) => Play(new AudioMessage(ControllerId, samples, length ?? samples.Length), targets); + + /// + /// Plays a wav file through this speaker.(File must be 16 bit, mono and 48khz.) + /// + /// The path to the wav file. + /// Whether to stream the audio or preload it. + /// Whether to destroy the speaker after playback. + /// Whether to loop the audio. + public void Play(string path, bool stream = false, bool destroyAfter = false, bool loop = false) + { + if (!File.Exists(path)) + throw new FileNotFoundException("The specified file does not exist.", path); + + if (!path.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException($"The file type '{Path.GetExtension(path)}' is not supported. Please use .wav file."); + + TryInitializePlayBack(); + Stop(); + + Loop = loop; + LastTrack = path; + DestroyAfter = destroyAfter; + source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(path); + playBackRoutine = Timing.RunCoroutine(PlayBackCoroutine().CancelWith(GameObject)); + } + + /// + /// Stops playback. + /// + public void Stop() + { + if (playBackRoutine.IsRunning) + { + Timing.KillCoroutines(playBackRoutine); + OnPlaybackStopped?.Invoke(); + } + + source?.Dispose(); + source = null; + } + + private void TryInitializePlayBack() + { + if (isPlayBackInitialized) + return; + + isPlayBackInitialized = true; + + frame = new float[FrameSize]; + resampleBuffer = Array.Empty(); + encoder = new(OpusApplicationType.Audio); + encoded = new byte[VoiceChatSettings.MaxEncodedSize]; + + AdminToyBase.OnRemoved += OnToyRemoved; + } + + private IEnumerator PlayBackCoroutine() + { + OnPlaybackStarted?.Invoke(); + + resampleTime = 0.0; + resampleBufferFilled = 0; + + float timeAccumulator = 0f; + + while (true) + { + timeAccumulator += Time.deltaTime; + + while (timeAccumulator >= FrameTime) + { + timeAccumulator -= FrameTime; + + if (isPitchDefault) + { + int read = source.Read(frame, 0, FrameSize); + if (read < FrameSize) + Array.Clear(frame, read, FrameSize - read); + } + else + { + ResampleFrame(); + } + + int len = encoder.Encode(frame, encoded); + + if (len > 2) + SendPacket(len); + + if (!source.Ended) + continue; + + OnPlaybackFinished?.Invoke(LastTrack); + + if (Loop) + { + source.Reset(); + OnPlaybackLooped?.Invoke(); + resampleTime = resampleBufferFilled = 0; + continue; + } + + if (DestroyAfter) + Destroy(); + else + Stop(); + + yield break; + } + + yield return Timing.WaitForOneFrame; + } + } + + private void ResampleFrame() + { + int requiredSize = (int)(FrameSize * Mathf.Abs(Pitch) * 2) + 10; + + if (resampleBuffer.Length < requiredSize) + { + resampleBuffer = new float[requiredSize]; + resampleTime = 0.0; + resampleBufferFilled = 0; + } + + int outputIdx = 0; + + while (outputIdx < FrameSize) + { + if (resampleBufferFilled == 0) + { + int toRead = resampleBuffer.Length - 4; + int actualRead = source.Read(resampleBuffer, 0, toRead); + + if (actualRead == 0) + { + while (outputIdx < FrameSize) + frame[outputIdx++] = 0f; + return; + } + + resampleBufferFilled = actualRead; + resampleTime = 0.0; + } + + int currentSample = (int)resampleTime; + + if (currentSample >= resampleBufferFilled - 1) + { + if (resampleBufferFilled > 0) + { + resampleBuffer[0] = resampleBuffer[resampleBufferFilled - 1]; + + int toRead = resampleBuffer.Length - 5; + int actualRead = source.Read(resampleBuffer, 1, toRead); + + if (actualRead == 0) + { + while (outputIdx < FrameSize) + frame[outputIdx++] = 0f; + return; + } + + resampleBufferFilled = actualRead + 1; + resampleTime -= currentSample; + } + else + { + resampleBufferFilled = 0; + } + + continue; + } + + double frac = resampleTime - currentSample; + float sample1 = resampleBuffer[currentSample]; + float sample2 = resampleBuffer[currentSample + 1]; + + frame[outputIdx++] = (float)(sample1 + ((sample2 - sample1) * frac)); + + resampleTime += Pitch; + } + } + + private void SendPacket(int len) + { + AudioMessage msg = new(ControllerId, encoded, len); + + switch (PlayMode) + { + case SpeakerPlayMode.Global: + NetworkServer.SendToReady(msg, Channel); + break; + + case SpeakerPlayMode.Player: + TargetPlayer?.Connection.Send(msg, Channel); + break; + + case SpeakerPlayMode.PlayerList: + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + { + NetworkMessages.Pack(msg, writer); + ArraySegment segment = writer.ToArraySegment(); + + foreach (Player ply in TargetPlayers) + { + ply?.Connection.Send(segment, Channel); + } + } + + break; + + case SpeakerPlayMode.Predicate: + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + { + NetworkMessages.Pack(msg, writer); + ArraySegment segment = writer.ToArraySegment(); + + foreach (Player ply in Player.List) + { + if (Predicate(ply)) + ply.Connection.Send(segment, Channel); + } + } + + break; + } + } + + private void OnToyRemoved(AdminToyBase toy) + { + if (toy != Base) + return; + + AdminToyBase.OnRemoved -= OnToyRemoved; + + Stop(); + + encoder?.Dispose(); + } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Interfaces/IPcmSource.cs b/EXILED/Exiled.API/Interfaces/IPcmSource.cs new file mode 100644 index 0000000000..680f568410 --- /dev/null +++ b/EXILED/Exiled.API/Interfaces/IPcmSource.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Interfaces +{ + using System; + + /// + /// Represents a source of PCM audio data. + /// + public interface IPcmSource : IDisposable + { + /// + /// Gets a value indicating whether the end of the PCM source has been reached. + /// + bool Ended { get; } + + /// + /// Gets the total duration of the audio in seconds. + /// + double TotalDuration { get; } + + /// + /// Gets or sets the current playback position in seconds. + /// + double CurrentTime { get; set; } + + /// + /// Reads a sequence of PCM samples into the specified buffer. + /// + /// The buffer to read the samples into. + /// The zero-based index in the buffer at which to begin storing the data read from the source. + /// The maximum number of samples to read. + /// The total number of samples read into the buffer. + int Read(float[] buffer, int offset, int count); + + /// + /// Seeks to the specified position in the PCM source. + /// + /// The position in seconds to seek to. + void Seek(double seconds); + + /// + /// Resets the PCM source to its initial state, allowing reading from the beginning. + /// + void Reset(); + } +} From 1c829c94432723f4c4075bd72efe5249478fb8e3 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:28:38 +0100 Subject: [PATCH 05/33] fix: Blinded -> Blindness --- EXILED/Exiled.CustomItems/API/Features/CustomGoggles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomGoggles.cs b/EXILED/Exiled.CustomItems/API/Features/CustomGoggles.cs index 769f5ad936..ca4fbdd32d 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomGoggles.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomGoggles.cs @@ -180,7 +180,7 @@ private void InternalRemove(Player player, Scp1344 goggles) if (CanBeRemoveSafely) { - player.DisableEffect(EffectType.Blinded); + player.DisableEffect(EffectType.Blindness); player.ReferenceHub?.DisableWearables(WearableElements.Scp1344Goggles); } From 59645fcec0f7fbe99cb8e190d6e9c88cddb6d10f Mon Sep 17 00:00:00 2001 From: michcio <89903081+michcio15@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:01:50 +0100 Subject: [PATCH 06/33] feat: add setter `Usable::IsUsing` (#753) * Adds a event that is called after player stops talking with 1576 * Simplify initialization of TransmissionEnded event. * should be no more warnings when build * Apply suggestions from code review Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> * added the suggestions to make it more exiled like * Moved to Player Handler * Update Scp1576TransmissionEnded.cs Change comments * Add `StartUsing` method to force item usage * adds very professional comment * Adds stop using * Refactor `IsUsing` property to include setter and remove redundant usage methods --------- Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Items/Usable.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Usable.cs b/EXILED/Exiled.API/Features/Items/Usable.cs index b58f6b8dd9..efdff0c2ff 100644 --- a/EXILED/Exiled.API/Features/Items/Usable.cs +++ b/EXILED/Exiled.API/Features/Items/Usable.cs @@ -67,9 +67,13 @@ internal Usable(ItemType type) } /// - /// Gets a value indicating whether the item is currently being used. + /// Gets or sets a value indicating whether the item is currently being used. /// - public bool IsUsing => Base.IsUsing; + public bool IsUsing + { + get => Base.IsUsing; + set => UsableItemsController.ServerEmulateMessage(Serial, value ? StatusMessage.StatusType.Start : StatusMessage.StatusType.Cancel); + } /// /// Gets or sets how long it takes to use the item. From 0ef3c68c8b084d748a11c8f03d77cbcb3ee22069 Mon Sep 17 00:00:00 2001 From: michcio <89903081+michcio15@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:02:25 +0100 Subject: [PATCH 07/33] feat: buttons array (#752) * Adds a event that is called after player stops talking with 1576 * Simplify initialization of TransmissionEnded event. * should be no more warnings when build * Apply suggestions from code review Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> * added the suggestions to make it more exiled like * Moved to Player Handler * Update Scp1576TransmissionEnded.cs Change comments * Expose Buttons property in Door class to access door button variants --------- Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com> Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Doors/Door.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index a8163f207c..81dcb427f4 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -16,6 +16,7 @@ namespace Exiled.API.Features.Doors using Exiled.API.Features.Core; using Exiled.API.Interfaces; using Interactables.Interobjects; + using Interactables.Interobjects.DoorButtons; using Interactables.Interobjects.DoorUtils; using MEC; using Mirror; @@ -298,6 +299,11 @@ public Vector3 Scale /// public ZoneType Zone => Room?.Zone ?? ZoneType.Unspecified; + /// + /// Gets the door's . + /// + public ButtonVariant[] Buttons => Base.Buttons; + /// /// Gets a containing all 's that are connected with . /// From be6d92d0bc356da0a9334fe93ab7cb4ffbf2a3b2 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:56:36 +0100 Subject: [PATCH 08/33] feat!: More more c#14 (#756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Speaker Toy Api * . * chore: trigger CI * added missing doc * Change default stream parameter to false in PlayWav * Improve XML documentation for Channel property Updated XML documentation for the Channel property to include a reference to Channels. * Update WavStreamSource.cs Update PreloadedPcmSource.cs Update Speaker.cs Create WavUtility.cs Update WavStreamSource.cs Update PreloadedPcmSource.cs Change default stream parameter to false in PlayWav Update PlayWav method to default stream to true Change TargetPlayers from List to HashSet * Update Speaker.cs * Update Speaker.cs * Update performance * Update Speaker.cs Update PreloadedPcmSource.cs Added another constructor for public usages Update PreloadedPcmSource.cs * Implemented audio seeking, event system, and precision timing - Introduced a full Event system to the Speaker class (Started, Finished, Stopped, Paused, Resumed). - Implemented `Seek()` functionality and added `CurrentTime` / `TotalDuration` properties. - Utilized `double` precision for all time related calculations to ensure accuracy. - Updated `WavStreamSource` and `PreloadedPcmSource` to support the new seeking logic. * Update Speaker.cs * Update WavUtility.cs * Update Speaker.cs * Update Speaker.cs * Update Speaker.cs * Update Speaker.cs * Added OnPlaybackLooped event * Update Speaker.cs Refactor: Optimize WavStreamSource with ArrayPool & dynamic buffering Feat: Add Pitch (it can be reversed if you wish) Update EXILED.props * Update to c# lang version 14 * Update Speaker.cs * Enum 4 byte to 1 byte * -Replaced 'File.ReadAllBytes' with 'ArrayPool.Shared' -Optimized WAV header skipping (string comparisons to direct uint32 hex checks) * Refactor * Initialize Channel property with ReliableOrdered2 * Refactor Speaker class properties * Update Speaker.cs * Update Speaker.cs * ağhhh * renamed method * Added Target Player Play Mode * C# 14 BreakingChange for LeftPlayerEvent * i miss that part :3 * Oups * fix: EffectType.Blinded -> EffectType.Blindness * Apply suggestions from code review * Revert "Apply suggestions from code review" This reverts commit 449bc275bdaa9c0b8333314547bfbe868762e802. --------- Co-authored-by: MS-crew <100300664+MS-crew@users.noreply.github.com> Co-authored-by: Mustafa SAVAŞ --- .../Exiled.API/Extensions/MirrorExtensions.cs | 9 ++---- EXILED/Exiled.API/Features/Camera.cs | 4 +-- EXILED/Exiled.API/Features/Core/EActor.cs | 18 +++++------ .../Core/StateMachine/StateController.cs | 11 ++++--- .../DamageHandlers/DamageHandlerBase.cs | 17 +++++------ EXILED/Exiled.API/Features/Generator.cs | 3 +- .../Features/Hazards/AmnesticCloudHazard.cs | 8 ++--- .../Features/Hazards/TantrumHazard.cs | 8 ++--- EXILED/Exiled.API/Features/Map.cs | 8 ++--- .../Features/Pickups/BodyArmorPickup.cs | 23 ++++---------- EXILED/Exiled.API/Features/Player.cs | 30 ++++++++----------- EXILED/Exiled.API/Features/Respawn.cs | 15 ++++------ EXILED/Exiled.API/Features/Roles/FpcRole.cs | 6 ++-- EXILED/Exiled.API/Features/Server.cs | 4 +-- EXILED/Exiled.API/Features/Warhead.cs | 4 +-- .../AggregateExpectationTypeResolver.cs | 2 +- .../Cassie/SendingCassieMessageEventArgs.cs | 19 +++++------- .../Map/ExplodingGrenadeEventArgs.cs | 6 ++-- .../EventArgs/Player/BanningEventArgs.cs | 10 +++---- .../EventArgs/Player/ChangingItemEventArgs.cs | 8 ++--- .../EventArgs/Player/ChangingRoleEventArgs.cs | 6 ++-- .../EventArgs/Player/DroppingAmmoEventArgs.cs | 14 ++------- .../EventArgs/Player/DroppingItemEventArgs.cs | 15 +++------- .../EventArgs/Player/EscapingEventArgs.cs | 6 ++-- .../InteractingShootingTargetEventArgs.cs | 15 ++++------ .../EventArgs/Player/KickingEventArgs.cs | 29 ++++++++---------- .../EventArgs/Player/LeftEventArgs.cs | 13 ++++---- .../Player/ReservedSlotsCheckEventArgs.cs | 7 ++--- .../EventArgs/Player/RevokingMuteEventArgs.cs | 22 ++++++++++++-- .../Scp914/ChangingKnobSettingEventArgs.cs | 6 ++-- .../Server/RespawningTeamEventArgs.cs | 8 ++--- .../EventArgs/Warhead/StartingEventArgs.cs | 16 ++++++++-- EXILED/Exiled.Events/Events.cs | 6 ++-- .../Patches/Generic/AirlockListAdd.cs | 2 +- .../Patches/Generic/CoffeeListAdd.cs | 2 +- .../Patches/Generic/HazardList.cs | 2 +- .../Exiled.Events/Patches/Generic/LiftList.cs | 2 +- 37 files changed, 166 insertions(+), 218 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 9fb9e47409..20f5b1a376 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -56,9 +56,6 @@ public static class MirrorExtensions private static readonly ReadOnlyDictionary ReadOnlyWriterExtensionsValue = new(WriterExtensionsValue); private static readonly ReadOnlyDictionary ReadOnlySyncVarDirtyBitsValue = new(SyncVarDirtyBitsValue); private static readonly ReadOnlyDictionary ReadOnlyRpcFullNamesValue = new(RpcFullNamesValue); - private static MethodInfo setDirtyBitsMethodInfoValue; - private static MethodInfo sendSpawnMessageMethodInfoValue; - private static string[] adminToyBaseSyncVarsValue; /// /// Gets corresponding to . @@ -153,17 +150,17 @@ public static ReadOnlyDictionary RpcFullNames /// /// Gets a 's . /// - public static MethodInfo SetDirtyBitsMethodInfo => setDirtyBitsMethodInfoValue ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit)); + public static MethodInfo SetDirtyBitsMethodInfo => field ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit)); /// /// Gets a NetworkServer.SendSpawnMessage's . /// - public static MethodInfo SendSpawnMessageMethodInfo => sendSpawnMessageMethodInfoValue ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static); + public static MethodInfo SendSpawnMessageMethodInfo => field ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static); /// /// Gets all sync var names. /// - public static string[] AdminToyBaseSyncVars => adminToyBaseSyncVarsValue ??= typeof(AdminToyBase).GetProperties().Where(property => property.Name.Contains("Network")).Select(property => property.Name).ToArray(); + public static string[] AdminToyBaseSyncVars => field ??= typeof(AdminToyBase).GetProperties().Where(property => property.Name.Contains("Network")).Select(property => property.Name).ToArray(); /// /// Plays a beep sound that only the target can hear. diff --git a/EXILED/Exiled.API/Features/Camera.cs b/EXILED/Exiled.API/Features/Camera.cs index fa17a77540..4eedc693dd 100644 --- a/EXILED/Exiled.API/Features/Camera.cs +++ b/EXILED/Exiled.API/Features/Camera.cs @@ -157,8 +157,6 @@ public class Camera : IWrapper, IWorldSpace ["SZ CAMERA TOY"] = CameraType.SzCameraToy, }; - private Room room; - /// /// Initializes a new instance of the class. /// @@ -220,7 +218,7 @@ internal Camera(Scp079Camera camera079) /// /// Gets the camera's . /// - public Room Room => room ??= Room.Get(Base.Room); + public Room Room => field ??= Room.Get(Base.Room); /// /// Gets the camera's . diff --git a/EXILED/Exiled.API/Features/Core/EActor.cs b/EXILED/Exiled.API/Features/Core/EActor.cs index f7f6beca77..b051974788 100644 --- a/EXILED/Exiled.API/Features/Core/EActor.cs +++ b/EXILED/Exiled.API/Features/Core/EActor.cs @@ -31,8 +31,6 @@ public abstract class EActor : EObject, IEntity, IWorldSpace private readonly HashSet componentsInChildren = HashSetPool.Pool.Get(); private CoroutineHandle serverTick; - private bool canEverTick; - private float fixedTickRate; /// /// Initializes a new instance of the class. @@ -42,10 +40,10 @@ protected EActor() { IsEditable = true; CanEverTick = true; - fixedTickRate = DefaultFixedTickRate; + FixedTickRate = DefaultFixedTickRate; PostInitialize(); - Timing.CallDelayed(fixedTickRate, () => OnBeginPlay()); - Timing.CallDelayed(fixedTickRate * 2, () => serverTick = Timing.RunCoroutine(ServerTick())); + Timing.CallDelayed(FixedTickRate, OnBeginPlay); + Timing.CallDelayed(FixedTickRate * 2, () => serverTick = Timing.RunCoroutine(ServerTick())); } /// @@ -99,15 +97,15 @@ public virtual Vector3 Scale /// public virtual bool CanEverTick { - get => canEverTick; + get; set { if (!IsEditable) return; - canEverTick = value; + field = value; - if (canEverTick) + if (field) { Timing.ResumeCoroutines(serverTick); return; @@ -122,13 +120,13 @@ public virtual bool CanEverTick /// public virtual float FixedTickRate { - get => fixedTickRate; + get; set { if (!IsEditable) return; - fixedTickRate = value; + field = value; } } diff --git a/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs b/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs index bce10ed688..ce193ec7a5 100644 --- a/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs +++ b/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs @@ -19,7 +19,6 @@ namespace Exiled.API.Features.Core.StateMachine public abstract class StateController : EActor { private readonly List states = new(); - private State currentState; /// /// Gets all handled states. @@ -31,14 +30,14 @@ public abstract class StateController : EActor /// public State CurrentState { - get => currentState; + get; set { - if (currentState.Id == value.Id) + if (field.Id == value.Id) return; - (PreviousState = currentState).OnExit(this); - (currentState = value).OnEnter(this); + (PreviousState = field).OnExit(this); + (field = value).OnEnter(this); OnStateChanged(); } @@ -75,7 +74,7 @@ public virtual void StateUpdate(State state) protected virtual void OnStateChanged() { EndStateMulticastDispatcher.InvokeAll(PreviousState); - BeginStateMulticastDispatcher.InvokeAll(currentState); + BeginStateMulticastDispatcher.InvokeAll(CurrentState); } } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs index 248e89452b..6a9b8f4562 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs @@ -28,9 +28,6 @@ namespace Exiled.API.Features.DamageHandlers /// public abstract class DamageHandlerBase { - private DamageType damageType; - private CassieAnnouncement cassieAnnouncement; - /// /// Initializes a new instance of the class. /// @@ -78,8 +75,8 @@ public enum Action : byte /// public virtual CassieAnnouncement CassieDeathAnnouncement { - get => cassieAnnouncement ?? Base.CassieDeathAnnouncement; - protected set => cassieAnnouncement = value; + get => field ?? Base.CassieDeathAnnouncement; + protected set; } /// @@ -94,11 +91,11 @@ public virtual DamageType Type { get { - if (damageType != DamageType.Unknown) - return damageType; + if (field != DamageType.Unknown) + return field; - damageType = GetDamageType(); - return damageType; + field = GetDamageType(); + return field; } protected set @@ -106,7 +103,7 @@ protected set if (!Enum.IsDefined(typeof(DamageType), value)) throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DamageType)); - damageType = value; + field = value; } } diff --git a/EXILED/Exiled.API/Features/Generator.cs b/EXILED/Exiled.API/Features/Generator.cs index 125bdd347b..8f3d6f41be 100644 --- a/EXILED/Exiled.API/Features/Generator.cs +++ b/EXILED/Exiled.API/Features/Generator.cs @@ -27,7 +27,6 @@ public class Generator : IWrapper, IWorldSpace, IStructureSync /// A of on the map. /// internal static readonly Dictionary Scp079GeneratorToGenerator = new(new ComponentsEqualityComparer()); - private Room room; /// /// Initializes a new instance of the class. @@ -63,7 +62,7 @@ internal Generator(Scp079Generator scp079Generator) /// /// Gets the generator's . /// - public Room Room => room ??= Room.FindParentRoom(GameObject); + public Room Room => field ??= Room.FindParentRoom(GameObject); /// /// Gets or sets the generator' state. diff --git a/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs b/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs index cc9bff8d67..3c2a982ab7 100644 --- a/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/AmnesticCloudHazard.cs @@ -15,8 +15,6 @@ namespace Exiled.API.Features.Hazards /// public class AmnesticCloudHazard : TemporaryHazard { - private static Scp939AmnesticCloudInstance amnesticCloudPrefab; - /// /// Initializes a new instance of the class. /// @@ -36,10 +34,10 @@ public static Scp939AmnesticCloudInstance AmnesticCloudPrefab { get { - if (amnesticCloudPrefab == null) - amnesticCloudPrefab = PrefabHelper.GetPrefab(PrefabType.AmnesticCloudHazard); + if (field == null) + field = PrefabHelper.GetPrefab(PrefabType.AmnesticCloudHazard); - return amnesticCloudPrefab; + return field; } } diff --git a/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs b/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs index 2e36627d8b..1660ba4dc9 100644 --- a/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs +++ b/EXILED/Exiled.API/Features/Hazards/TantrumHazard.cs @@ -18,8 +18,6 @@ namespace Exiled.API.Features.Hazards /// public class TantrumHazard : TemporaryHazard { - private static TantrumEnvironmentalHazard tantrumPrefab; - /// /// Initializes a new instance of the class. /// @@ -37,10 +35,10 @@ public static TantrumEnvironmentalHazard TantrumPrefab { get { - if (tantrumPrefab == null) - tantrumPrefab = PrefabHelper.GetPrefab(PrefabType.TantrumObj); + if (field == null) + field = PrefabHelper.GetPrefab(PrefabType.TantrumObj); - return tantrumPrefab; + return field; } } diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index c4feacd0cb..4e8b3547ed 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -46,10 +46,6 @@ public static class Map /// internal static List TeleportsValue = new(); - private static AmbientSoundPlayer ambientSoundPlayer; - - private static SqueakSpawner squeakSpawner; - /// /// Gets a value indicating whether decontamination has begun in the light containment zone. /// @@ -123,12 +119,12 @@ public static bool IsDecontaminationEnabled /// /// Gets the . /// - public static AmbientSoundPlayer AmbientSoundPlayer => ambientSoundPlayer ??= ReferenceHub._hostHub.GetComponent(); + public static AmbientSoundPlayer AmbientSoundPlayer => field ??= ReferenceHub._hostHub.GetComponent(); /// /// Gets the . /// - public static SqueakSpawner SqueakSpawner => squeakSpawner ??= Object.FindFirstObjectByType(); + public static SqueakSpawner SqueakSpawner => field ??= Object.FindFirstObjectByType(); /// /// Sends a staff message to all players online with permission. diff --git a/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs b/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs index 1b63763531..3de6897413 100644 --- a/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs +++ b/EXILED/Exiled.API/Features/Pickups/BodyArmorPickup.cs @@ -27,9 +27,6 @@ namespace Exiled.API.Features.Pickups /// public class BodyArmorPickup : Pickup, IWrapper { - private int helmetEfficacy; - private int vestEfficacy; - /// /// Initializes a new instance of the class. /// @@ -79,20 +76,12 @@ internal BodyArmorPickup(ItemType type) /// /// Gets or sets how strong the helmet on the armor is. /// - public int HelmetEfficacy - { - get => helmetEfficacy; - set => helmetEfficacy = value; - } + public int HelmetEfficacy { get; set; } /// /// Gets or sets how strong the vest on the armor is. /// - public int VestEfficacy - { - get => vestEfficacy; - set => vestEfficacy = value; - } + public int VestEfficacy { get; set; } /// /// Gets or sets how much faster stamina will drain when wearing this armor. @@ -131,8 +120,8 @@ internal override void ReadItemInfo(Item item) base.ReadItemInfo(item); if (item is Armor armoritem) { - helmetEfficacy = armoritem.HelmetEfficacy; - vestEfficacy = armoritem.VestEfficacy; + HelmetEfficacy = armoritem.HelmetEfficacy; + VestEfficacy = armoritem.VestEfficacy; StaminaUseMultiplier = armoritem.StaminaUseMultiplier; StaminaRegenMultiplier = armoritem.StaminaRegenMultiplier; AmmoLimits = armoritem.AmmoLimits; @@ -146,8 +135,8 @@ protected override void InitializeProperties(ItemBase itemBase) base.InitializeProperties(itemBase); if (itemBase is BodyArmor armoritem) { - helmetEfficacy = armoritem.HelmetEfficacy; - vestEfficacy = armoritem.VestEfficacy; + HelmetEfficacy = armoritem.HelmetEfficacy; + VestEfficacy = armoritem.VestEfficacy; StaminaUseMultiplier = armoritem._staminaUseMultiplier; StaminaRegenMultiplier = armoritem.StaminaRegenMultiplier; AmmoLimits = armoritem.AmmoLimits.Select(limit => (ArmorAmmoLimit)limit); diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index bfac75fbe0..522a0a2113 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -95,10 +95,6 @@ public class Player : TypeCastObject, IEntity, IWorldSpace private readonly HashSet componentsInChildren = new(); - private ReferenceHub referenceHub; - - private Role role; - /// /// Initializes a new instance of the class. /// @@ -192,10 +188,10 @@ public Player(GameObject gameObject) /// public ReferenceHub ReferenceHub { - get => referenceHub; + get; private set { - referenceHub = value ?? throw new NullReferenceException("Player's ReferenceHub cannot be null!"); + field = value ?? throw new NullReferenceException("Player's ReferenceHub cannot be null!"); GameObject = value.gameObject; HintDisplay = value.hints; Inventory = value.inventory; @@ -278,7 +274,7 @@ public int Id /// /// Gets the player's user id. /// - public string UserId => referenceHub.authManager.UserId; + public string UserId => ReferenceHub.authManager.UserId; /// /// Gets the player's user id without the authentication. @@ -599,11 +595,11 @@ public PlayerPermissions RemoteAdminPermissions /// public Role Role { - get => role ??= Role.Create(RoleManager.CurrentRole); + get => field ??= Role.Create(RoleManager.CurrentRole); internal set { - PreviousRole = role?.Type ?? RoleTypeId.None; - role = value; + PreviousRole = field?.Type ?? RoleTypeId.None; + field = value; } } @@ -654,7 +650,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public bool IsJumping { get => Role is FpcRole fpc && fpc.FirstPersonController.FpcModule.Motor.JumpController.IsJumping; - set => _ = Role is FpcRole fpc ? fpc.FirstPersonController.FpcModule.Motor.JumpController.IsJumping = value : _ = value; + set => (Role as FpcRole)?.FirstPersonController.FpcModule.Motor.JumpController.IsJumping = value; } /// @@ -863,7 +859,7 @@ public bool IsGodModeEnabled public byte UnitId { get => Role.Base is PlayerRoles.HumanRole humanRole ? humanRole.UnitNameId : byte.MinValue; - set => _ = Role.Base is PlayerRoles.HumanRole humanRole ? humanRole.UnitNameId = value : _ = value; + set => (Role.Base as PlayerRoles.HumanRole)?.UnitNameId = value; } /// @@ -1074,7 +1070,7 @@ public string GroupName /// /// /// - public IEnumerable ActiveEffects => referenceHub.playerEffectsController.AllEffects.Where(effect => effect.Intensity > 0); + public IEnumerable ActiveEffects => ReferenceHub.playerEffectsController.AllEffects.Where(effect => effect.Intensity > 0); /// /// Gets or sets the player's group. @@ -1962,7 +1958,7 @@ public void Handcuff() { ReferenceHub.inventory.SetDisarmedStatus(null); - DisarmedPlayers.Entries.Add(new DisarmedPlayers.DisarmedEntry(referenceHub.networkIdentity.netId, 0U)); + DisarmedPlayers.Entries.Add(new DisarmedPlayers.DisarmedEntry(ReferenceHub.networkIdentity.netId, 0U)); new DisarmedPlayersListMessage(DisarmedPlayers.Entries).SendToAuthenticated(0); } @@ -2183,7 +2179,7 @@ public int RemoveItem(Func predicate, bool destroy = true) /// /// The message to be sent. /// The message color. - public void SendConsoleMessage(string message, string color) => referenceHub.gameConsoleTransmission.SendToClient(message, color); + public void SendConsoleMessage(string message, string color) => ReferenceHub.gameConsoleTransmission.SendToClient(message, color); /// /// Disconnects the player. @@ -2590,7 +2586,7 @@ public ushort GetAmmoLimit(AmmoType type, bool ignoreArmor = false) return ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FirstOrDefault(x => x.AmmoType == itemType).Limit; } - return InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), referenceHub); + return InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), ReferenceHub); } /// @@ -2666,7 +2662,7 @@ public sbyte GetCategoryLimit(ItemCategory category, bool ignoreArmor = false) return ServerConfigSynchronizer.Singleton.CategoryLimits[index]; } - sbyte limit = InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, referenceHub); + sbyte limit = InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, ReferenceHub); return limit == -1 ? (sbyte)1 : limit; } diff --git a/EXILED/Exiled.API/Features/Respawn.cs b/EXILED/Exiled.API/Features/Respawn.cs index 0f34495520..fba56ca96d 100644 --- a/EXILED/Exiled.API/Features/Respawn.cs +++ b/EXILED/Exiled.API/Features/Respawn.cs @@ -25,9 +25,6 @@ namespace Exiled.API.Features /// public static class Respawn { - private static GameObject ntfHelicopterGameObject; - private static GameObject chaosCarGameObject; - /// /// Gets the of paused 's. /// @@ -45,10 +42,10 @@ public static GameObject NtfHelicopter { get { - if (ntfHelicopterGameObject == null) - ntfHelicopterGameObject = GameObject.Find("Chopper"); + if (field == null) + field = GameObject.Find("Chopper"); - return ntfHelicopterGameObject; + return field; } } @@ -59,10 +56,10 @@ public static GameObject ChaosVan { get { - if (chaosCarGameObject == null) - chaosCarGameObject = GameObject.Find("CIVanArrive"); + if (field == null) + field = GameObject.Find("CIVanArrive"); - return chaosCarGameObject; + return field; } } diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index 9a1156833e..cf0cdae5c1 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -27,8 +27,6 @@ namespace Exiled.API.Features.Roles /// public abstract class FpcRole : Role, IVoiceRole { - private bool isUsingStamina = true; - /// /// Initializes a new instance of the class. /// @@ -190,12 +188,12 @@ public bool MovementDetected /// public bool IsUsingStamina { - get => isUsingStamina; + get; set { if (!value) Owner.ResetStamina(); - isUsingStamina = value; + field = value; } } diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 825cfb3566..0d5809912e 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -32,8 +32,6 @@ namespace Exiled.API.Features /// public static class Server { - private static MethodInfo sendSpawnMessage; - /// /// Gets a dictionary that pairs assemblies with their associated plugins. /// @@ -53,7 +51,7 @@ public static class Server /// /// Gets the cached . /// - public static MethodInfo SendSpawnMessage => sendSpawnMessage ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static); + public static MethodInfo SendSpawnMessage => field ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static); /// /// Gets or sets the name of the server. diff --git a/EXILED/Exiled.API/Features/Warhead.cs b/EXILED/Exiled.API/Features/Warhead.cs index f261577770..3f1e12c516 100644 --- a/EXILED/Exiled.API/Features/Warhead.cs +++ b/EXILED/Exiled.API/Features/Warhead.cs @@ -20,8 +20,6 @@ namespace Exiled.API.Features /// public static class Warhead { - private static AlphaWarheadOutsitePanel alphaWarheadOutsitePanel; - /// /// Gets the cached component. /// @@ -35,7 +33,7 @@ public static class Warhead /// /// Gets the cached component. /// - public static AlphaWarheadOutsitePanel OutsitePanel => alphaWarheadOutsitePanel != null ? alphaWarheadOutsitePanel : (alphaWarheadOutsitePanel = UnityEngine.Object.FindFirstObjectByType()); + public static AlphaWarheadOutsitePanel OutsitePanel => field != null ? field : (field = Object.FindFirstObjectByType()); /// /// Gets the of the warhead lever. diff --git a/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs b/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs index 646c34bb50..8f701628a3 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/Parsers/AggregateExpectationTypeResolver.cs @@ -52,7 +52,7 @@ public AggregateExpectationTypeResolver(INamingConvention namingConvention) } /// - public Type BaseType => typeof(CustomAbility); + public Type BaseType => field ??= typeof(CustomAbility); /// public bool TryResolve(ParsingEventBuffer buffer, out Type? suggestedType) diff --git a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs index abd2f4877f..d5dbbef859 100644 --- a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs @@ -20,12 +20,8 @@ namespace Exiled.Events.EventArgs.Cassie /// public class SendingCassieMessageEventArgs : IDeniableEvent { - private readonly CassieAnnouncement announcement; private readonly CassieTtsPayload payload; - private string customSubtitles; - private float glitchScale; - /// /// Initializes a new instance of the class. /// @@ -35,7 +31,7 @@ public class SendingCassieMessageEventArgs : IDeniableEvent /// public SendingCassieMessageEventArgs(CassieAnnouncement annc, bool isAllowed = true) { - announcement = annc; + Announcement = annc; payload = annc.Payload; Words = payload.Content; @@ -84,13 +80,13 @@ public SendingCassieMessageEventArgs(CassieAnnouncement annc, bool isAllowed = t /// public string CustomSubtitles { - get => customSubtitles; + get; set { - if (customSubtitles != value) + if (field != value) SubtitleSource = CassieTtsPayload.SubtitleMode.Custom; - customSubtitles = value; + field = value; } } @@ -104,13 +100,13 @@ public string CustomSubtitles /// public float GlitchScale { - get => glitchScale; + get; set { if (!MakeNoise && value is not 0) MakeNoise = true; - glitchScale = value; + field = value; } } @@ -157,7 +153,7 @@ public CassieAnnouncement Announcement newPayload = new CassieTtsPayload(Words, CustomSubtitles, MakeHold); } - return announcement switch + return field switch { CassieScpTerminationAnnouncement => @@ -169,6 +165,7 @@ public CassieAnnouncement Announcement _ => new CassieAnnouncement(newPayload, 0, GlitchScale / (API.Features.Warhead.IsDetonated ? 2F : 1F) * (MakeNoise ? 1F : 0F)), }; } + private set; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs index a0bba7e674..474995b3b8 100644 --- a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs @@ -27,8 +27,6 @@ namespace Exiled.Events.EventArgs.Map /// public class ExplodingGrenadeEventArgs : IPlayerEvent, IDeniableEvent, IPickupEvent { - private ExplosionType explosionType; - /// /// Initializes a new instance of the class. /// @@ -125,8 +123,8 @@ public ExplodingGrenadeEventArgs(Player thrower, EffectGrenade grenade, HashSet< /// Explosion that are not from will return and can't be modified. public ExplosionType ExplosionType { - get => explosionType; - set => explosionType = Projectile is ExplosionGrenadeProjectile ? value : ExplosionType.Custom; + get; + set => field = Projectile is ExplosionGrenadeProjectile ? value : ExplosionType.Custom; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs index 86d15bf8d8..5679b15f76 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs @@ -17,8 +17,6 @@ namespace Exiled.Events.EventArgs.Player /// public class BanningEventArgs : KickingEventArgs { - private long duration; - /// /// Initializes a new instance of the class. /// @@ -40,16 +38,16 @@ public BanningEventArgs(Player target, Player issuer, ICommandSender commandSend /// public long Duration { - get => duration; + get; set { - if (duration == value) + if (field == value) return; if (Events.Instance.Config.ShouldLogBans) - LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed Ban duration: {duration} to {value} for ID: {Target.UserId}"); + LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed Ban duration: {field} to {value} for ID: {Target.UserId}"); - duration = value; + field = value; } } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs index 6978172b54..ee305c7ada 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs @@ -21,8 +21,6 @@ namespace Exiled.Events.EventArgs.Player /// public class ChangingItemEventArgs : IPlayerEvent, IDeniableEvent, IItemEvent { - private Item newItem; - /// /// Initializes a new instance of the class. /// @@ -35,7 +33,7 @@ public class ChangingItemEventArgs : IPlayerEvent, IDeniableEvent, IItemEvent public ChangingItemEventArgs(Player player, ItemBase newItem) { Player = player; - this.newItem = Item.Get(newItem); + Item = Item.Get(newItem); } /// @@ -43,13 +41,13 @@ public ChangingItemEventArgs(Player player, ItemBase newItem) /// public Item Item { - get => newItem; + get; set { if (value != null && !Player.Inventory.UserInventory.Items.TryGetValue(value.Serial, out _)) throw new InvalidOperationException("ev.NewItem cannot be set to an item they do not have."); - newItem = value; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs index 898ab9fcd6..052fff48c6 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs @@ -22,8 +22,6 @@ namespace Exiled.Events.EventArgs.Player /// public class ChangingRoleEventArgs : IPlayerEvent, IDeniableEvent { - private RoleTypeId newRole; - /// /// Initializes a new instance of the class. /// @@ -66,7 +64,7 @@ public ChangingRoleEventArgs(Player player, RoleTypeId newRole, RoleChangeReason /// public RoleTypeId NewRole { - get => newRole; + get; set { InventoryRoleInfo inventory = value.GetInventory(); @@ -80,7 +78,7 @@ public RoleTypeId NewRole foreach (KeyValuePair ammoPair in inventory.Ammo) Ammo.Add(ammoPair.Key, ammoPair.Value); - newRole = value; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/DroppingAmmoEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DroppingAmmoEventArgs.cs index 0b379c903a..999516aef2 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DroppingAmmoEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DroppingAmmoEventArgs.cs @@ -19,8 +19,6 @@ namespace Exiled.Events.EventArgs.Player /// public class DroppingAmmoEventArgs : IPlayerEvent, IDeniableEvent { - private bool isAllowed = true; - /// /// Initializes a new instance of the class. /// @@ -66,18 +64,12 @@ public DroppingAmmoEventArgs(Player player, ItemType itemType, ushort amount, bo /// public bool IsAllowed { - get - { - if (Player.Role == RoleTypeId.Spectator) - isAllowed = true; - return isAllowed; - } - + get; set { if (Player.Role == RoleTypeId.Spectator) - value = true; - isAllowed = value; + return; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/DroppingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/DroppingItemEventArgs.cs index cf15df90d3..16006c8164 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/DroppingItemEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/DroppingItemEventArgs.cs @@ -21,8 +21,6 @@ namespace Exiled.Events.EventArgs.Player /// public class DroppingItemEventArgs : IItemEvent, IDeniableEvent { - private bool isAllowed = true; - /// /// Initializes a new instance of the class. /// @@ -44,6 +42,7 @@ public DroppingItemEventArgs(Player player, ItemBase item, bool isThrown, bool i Item = Item.Get(item); IsAllowed = isAllowed; IsThrown = isThrown; + IsAllowed = isAllowed; } /// @@ -56,18 +55,12 @@ public DroppingItemEventArgs(Player player, ItemBase item, bool isThrown, bool i /// public bool IsAllowed { - get - { - if (Player.Role == RoleTypeId.Spectator) - isAllowed = true; - return isAllowed; - } - + get; set { if (Player.Role == RoleTypeId.Spectator) - value = true; - isAllowed = value; + return; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 2e94fd0e6d..7333b3b3f8 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs @@ -18,8 +18,6 @@ namespace Exiled.Events.EventArgs.Player /// public class EscapingEventArgs : IPlayerEvent, IDeniableEvent { - private EscapeScenario escapeScenario; - /// /// Initializes a new instance of the class. /// @@ -55,8 +53,8 @@ public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeSc /// public EscapeScenario EscapeScenario { - get => (escapeScenario is EscapeScenario.None && IsAllowed) ? EscapeScenario.CustomEscape : escapeScenario; - set => escapeScenario = value; + get => (field is EscapeScenario.None && IsAllowed) ? EscapeScenario.CustomEscape : field; + set; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs index 086dd409bc..9555a12861 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs @@ -24,9 +24,6 @@ namespace Exiled.Events.EventArgs.Player /// public class InteractingShootingTargetEventArgs : IPlayerEvent, IDeniableEvent { - private int autoResetTime; - private int maxHp; - /// /// Initializes a new instance of the class. /// @@ -54,8 +51,8 @@ public InteractingShootingTargetEventArgs(Player player, ShootingTarget shooting ShootingTarget = ShootingTargetToy.Get(shootingTarget); TargetButton = targetButton; IsAllowed = isAllowed; - this.maxHp = maxHp; - this.autoResetTime = autoResetTime; + NewMaxHp = maxHp; + NewAutoResetTime = autoResetTime; } /// @@ -73,12 +70,12 @@ public InteractingShootingTargetEventArgs(Player player, ShootingTarget shooting /// public int NewMaxHp { - get => maxHp; + get; set { if (!ShootingTarget.IsSynced) throw new InvalidOperationException("Attempted to set MaxHp while target is in local mode. Set target's IsSynced to true before setting IsAllowed."); - maxHp = Mathf.Clamp(value, 1, 256); + field = Mathf.Clamp(value, 1, 256); } } @@ -87,12 +84,12 @@ public int NewMaxHp /// public int NewAutoResetTime { - get => autoResetTime; + get; set { if (!ShootingTarget.IsSynced) throw new InvalidOperationException("Attempted to set AutoResetTime while target is in local mode. Set target's IsSynced to true before setting IsAllowed."); - autoResetTime = Mathf.Clamp(value, 0, 10); + field = Mathf.Clamp(value, 0, 10); } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs index cfa2864b85..3fc6c1221d 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs @@ -19,9 +19,6 @@ namespace Exiled.Events.EventArgs.Player public class KickingEventArgs : IPlayerEvent, IDeniableEvent { private readonly string startkickmessage; - private bool isAllowed; - private Player issuer; - private Player target; /// /// Initializes a new instance of the class. @@ -59,16 +56,16 @@ public KickingEventArgs(Player target, Player issuer, ICommandSender commandSend /// public Player Target { - get => target; + get; set { - if (value is null || target == value) + if (value is null || field == value) return; - if (Events.Instance.Config.ShouldLogBans && target is not null) - LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed the banned player from user {target.Nickname} ({target.UserId}) to {value.Nickname} ({value.UserId})"); + if (Events.Instance.Config.ShouldLogBans && field is not null) + LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed the banned player from user {field.Nickname} ({field.UserId}) to {value.Nickname} ({value.UserId})"); - target = value; + field = value; } } @@ -87,16 +84,16 @@ public Player Target /// public bool IsAllowed { - get => isAllowed; + get; set { - if (isAllowed == value) + if (field == value) return; if (Events.Instance.Config.ShouldLogBans) LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" {(value ? "allowed" : "denied")} banning user with ID: {Target.UserId}"); - isAllowed = value; + field = value; } } @@ -105,16 +102,16 @@ public bool IsAllowed /// public Player Player { - get => issuer; + get; set { - if (value is null || issuer == value) + if (value is null || field == value) return; - if (Events.Instance.Config.ShouldLogBans && issuer is not null) - LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed the ban issuer from user {issuer.Nickname} ({issuer.UserId}) to {value.Nickname} ({value.UserId})"); + if (Events.Instance.Config.ShouldLogBans && field is not null) + LogBanChange(Assembly.GetCallingAssembly().GetName().Name, $" changed the ban issuer from user {field.Nickname} ({field.UserId}) to {value.Nickname} ({value.UserId})"); - issuer = value; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/LeftEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/LeftEventArgs.cs index 419a185713..84e5e8735a 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/LeftEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/LeftEventArgs.cs @@ -8,19 +8,22 @@ namespace Exiled.Events.EventArgs.Player { using API.Features; + using Exiled.Events.EventArgs.Interfaces; /// /// Contains all information after a disconnects from the server. /// - public class LeftEventArgs : JoinedEventArgs + public class LeftEventArgs : IPlayerEvent { /// /// Initializes a new instance of the class. /// /// The player who left the server. - public LeftEventArgs(Player player) - : base(player) - { - } + public LeftEventArgs(Player player) => Player = player; + + /// + /// Gets the left player. + /// + public Player Player { get; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs index ebb22cf345..cb949391ca 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs @@ -15,8 +15,6 @@ namespace Exiled.Events.EventArgs.Player /// public class ReservedSlotsCheckEventArgs : IExiledEvent, IDeniableEvent { - private ReservedSlotEventResult reservedSlotEventResult = ReservedSlotEventResult.UseBaseGameSystem; - /// /// Initializes a new instance of the class. /// @@ -31,6 +29,7 @@ public ReservedSlotsCheckEventArgs(bool hasReservedSlot, string userId) UserId = userId; HasReservedSlot = hasReservedSlot; IsAllowed = hasReservedSlot; + Result = ReservedSlotEventResult.UseBaseGameSystem; } /// @@ -53,7 +52,7 @@ public ReservedSlotsCheckEventArgs(bool hasReservedSlot, string userId) /// public ReservedSlotEventResult Result { - get => reservedSlotEventResult; + get; set { switch (value) @@ -71,7 +70,7 @@ public ReservedSlotEventResult Result return; } - reservedSlotEventResult = value; + field = value; } } } diff --git a/EXILED/Exiled.Events/EventArgs/Player/RevokingMuteEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RevokingMuteEventArgs.cs index eb3251599a..599ac864bf 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/RevokingMuteEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/RevokingMuteEventArgs.cs @@ -8,11 +8,12 @@ namespace Exiled.Events.EventArgs.Player { using API.Features; + using Exiled.Events.EventArgs.Interfaces; /// /// Contains all information before unmuting a player. /// - public class RevokingMuteEventArgs : IssuingMuteEventArgs + public class RevokingMuteEventArgs : IPlayerEvent, IDeniableEvent { /// /// Initializes a new instance of the class. @@ -27,8 +28,25 @@ public class RevokingMuteEventArgs : IssuingMuteEventArgs /// Indicates whether the player can be unmuted. /// public RevokingMuteEventArgs(Player player, bool isIntercom, bool isAllowed = true) - : base(player, isIntercom, isAllowed) { + Player = player; + IsIntercom = isIntercom; + IsAllowed = isAllowed; } + + /// + /// Gets the player who's being revoking the mute. + /// + public Player Player { get; } + + /// + /// Gets or sets a value indicating whether the player is being revoking intercom muted. + /// + public bool IsIntercom { get; set; } + + /// + /// Gets or sets a value indicating whether the player can be revoked muted. + /// + public bool IsAllowed { get; set; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs index b7af91e658..4874f9de7b 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs @@ -18,8 +18,6 @@ namespace Exiled.Events.EventArgs.Scp914 /// public class ChangingKnobSettingEventArgs : IPlayerEvent, IDeniableEvent { - private Scp914KnobSetting knobSetting; - /// /// Initializes a new instance of the class. /// @@ -44,8 +42,8 @@ public ChangingKnobSettingEventArgs(Player player, Scp914KnobSetting knobSetting /// public Scp914KnobSetting KnobSetting { - get => knobSetting; - set => knobSetting = value > Scp914KnobSetting.VeryFine ? Scp914KnobSetting.Coarse : value; + get; + set => field = value > Scp914KnobSetting.VeryFine ? Scp914KnobSetting.Coarse : value; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs index df999d90dd..6e017b2816 100644 --- a/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs @@ -24,8 +24,6 @@ namespace Exiled.Events.EventArgs.Server /// public class RespawningTeamEventArgs : IDeniableEvent { - private int maximumRespawnAmount; - /// /// Initializes a new instance of the class. /// @@ -69,16 +67,16 @@ public RespawningTeamEventArgs(List players, int maxRespawn, SpawnableWa /// public int MaximumRespawnAmount { - get => maximumRespawnAmount; + get; set { - if (value < maximumRespawnAmount) + if (value < field) { if (Players.Count > value) Players.RemoveRange(value, Players.Count - value); } - maximumRespawnAmount = value; + field = value; } } diff --git a/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs index 6f006c5299..214c8f8c2b 100644 --- a/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs @@ -8,11 +8,12 @@ namespace Exiled.Events.EventArgs.Warhead { using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; /// /// Contains all information before starting the warhead. /// - public class StartingEventArgs : StoppingEventArgs + public class StartingEventArgs : IPlayerEvent, IDeniableEvent { /// /// Initializes a new instance of the class. @@ -21,14 +22,25 @@ public class StartingEventArgs : StoppingEventArgs /// Indicating whether the nuke was set off automatically. /// Indicating whether the event can be executed. public StartingEventArgs(Player player, bool isAuto, bool isAllowed = true) - : base(player, isAllowed) { IsAuto = isAuto; + Player = player ?? Server.Host; + IsAllowed = isAllowed; } /// /// Gets or sets a value indicating whether the nuke was set off automatically. /// public bool IsAuto { get; set; } + + /// + /// Gets or sets a value indicating whether the warhead can be started. + /// + public bool IsAllowed { get; set; } + + /// + /// Gets the player who's going to start the warhead. + /// + public Player Player { get; } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index c941abd7ca..f01919aff5 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -30,12 +30,10 @@ namespace Exiled.Events /// public sealed class Events : Plugin { - private static Events instance; - /// /// Gets the plugin instance. /// - public static Events Instance => instance; + public static Events Instance { get; private set; } /// public override PluginPriority Priority { get; } = PluginPriority.First; @@ -48,7 +46,7 @@ public sealed class Events : Plugin /// public override void OnEnabled() { - instance = this; + Instance = this; base.OnEnabled(); Stopwatch watch = Stopwatch.StartNew(); diff --git a/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs b/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs index d8cab19b53..6da34a65eb 100644 --- a/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs +++ b/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs @@ -21,7 +21,7 @@ internal class AirlockListAdd { private static void Postfix(AirlockController __instance) { - _ = new API.Features.Doors.AirlockController(__instance); + new API.Features.Doors.AirlockController(__instance); } } diff --git a/EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs b/EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs index 1761e27416..d7eb560618 100644 --- a/EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs +++ b/EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs @@ -19,7 +19,7 @@ internal class CoffeeListAdd { private static void Postfix(global::Coffee __instance) { - _ = new Coffee(__instance); + new Coffee(__instance); } } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Generic/HazardList.cs b/EXILED/Exiled.Events/Patches/Generic/HazardList.cs index 34aacf25d1..db16697730 100644 --- a/EXILED/Exiled.Events/Patches/Generic/HazardList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/HazardList.cs @@ -21,7 +21,7 @@ internal class HazardList { [HarmonyPatch(typeof(EnvironmentalHazard), nameof(EnvironmentalHazard.Start))] [HarmonyPostfix] - private static void Adding(EnvironmentalHazard __instance) => _ = Hazard.Get(__instance); + private static void Adding(EnvironmentalHazard __instance) => Hazard.Get(__instance); [HarmonyPatch(typeof(EnvironmentalHazard), nameof(EnvironmentalHazard.OnDestroy))] [HarmonyPostfix] diff --git a/EXILED/Exiled.Events/Patches/Generic/LiftList.cs b/EXILED/Exiled.Events/Patches/Generic/LiftList.cs index d4b212dcd5..d088dbe3ab 100644 --- a/EXILED/Exiled.Events/Patches/Generic/LiftList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/LiftList.cs @@ -21,7 +21,7 @@ internal class LiftList { [HarmonyPatch(typeof(ElevatorChamber), nameof(ElevatorChamber.Start))] [HarmonyPostfix] - private static void Adding(ElevatorChamber __instance) => _ = new Lift(__instance); + private static void Adding(ElevatorChamber __instance) => new Lift(__instance); [HarmonyPatch(typeof(ElevatorChamber), nameof(ElevatorChamber.OnDestroy))] [HarmonyPostfix] From 690222436b05e763ba6fda34d1984cfad83b1f24 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:11:10 +0100 Subject: [PATCH 09/33] fix: IsUsingStamina missing default set to True --- EXILED/Exiled.API/Features/Roles/FpcRole.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs index cf0cdae5c1..e61e866ea7 100644 --- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs +++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs @@ -35,6 +35,7 @@ protected FpcRole(FpcStandardRoleBase baseRole) : base(baseRole) { FirstPersonController = baseRole; + IsUsingStamina = true; } /// From adfa2fb27b9f5f111f64861a5c31c24543f53dba Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sat, 7 Mar 2026 00:03:24 +0300 Subject: [PATCH 10/33] fix: Players base Category Limits (#764) * fix * someonefix * set reset validaiton --- EXILED/Exiled.API/Features/Player.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 522a0a2113..6f4f500e65 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2652,9 +2652,9 @@ public void ResetAmmoLimit(AmmoType ammoType) /// The maximum amount of items in the category that the player can hold. public sbyte GetCategoryLimit(ItemCategory category, bool ignoreArmor = false) { - int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + int index = (int)category; - if (ignoreArmor && index != -1) + if (ignoreArmor) { if (CustomCategoryLimits.TryGetValue(category, out sbyte customLimit)) return customLimit; @@ -2676,11 +2676,11 @@ public sbyte GetCategoryLimit(ItemCategory category, bool ignoreArmor = false) /// The number that will define the new limit. public void SetCategoryLimit(ItemCategory category, sbyte limit) { - int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + int index = (int)category; - if (index == -1) + if (index < 0 || index >= ServerConfigSynchronizer.Singleton.CategoryLimits.Count) { - Log.Error($"{nameof(Player)}.{nameof(SetCategoryLimit)}(ItemCategory, sbyte): Cannot set category limit for ItemCategory.{category}."); + Log.Error($"{nameof(Player)}.{nameof(SetCategoryLimit)}(ItemCategory, sbyte): Cannot set category limit for ItemCategory.{category}. Index out of bounds."); return; } @@ -2702,18 +2702,16 @@ public void SetCategoryLimit(ItemCategory category, sbyte limit) /// The of the category to reset. public void ResetCategoryLimit(ItemCategory category) { - int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category); + int index = (int)category; - if (index == -1) + if (index < 0 || index >= ServerConfigSynchronizer.Singleton.CategoryLimits.Count) { - Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory, sbyte): Cannot reset category limit for ItemCategory.{category}."); + Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory, sbyte): Cannot reset category limit for ItemCategory.{category}. Index out of bounds."); return; } if (!HasCustomCategoryLimit(category)) - { return; - } CustomCategoryLimits.Remove(category); @@ -4051,4 +4049,4 @@ public override int GetHashCode() /// A string containing Player-related data. public override string ToString() => $"{Id} ({Nickname}) [{UserId}] *{(Role is null ? "No role" : Role)}*"; } -} +} \ No newline at end of file From ab1f433e26fd4c72f575b43764b1f30ac480c9b5 Mon Sep 17 00:00:00 2001 From: Unbistrackted <112902220+Unbistrackted@users.noreply.github.com> Date: Mon, 23 Feb 2026 02:45:15 -0300 Subject: [PATCH 11/33] feat(warhead): Add ``IsOnCooldown`` property (#757) --- EXILED/Exiled.API/Features/Warhead.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Warhead.cs b/EXILED/Exiled.API/Features/Warhead.cs index 3f1e12c516..d26cf33e84 100644 --- a/EXILED/Exiled.API/Features/Warhead.cs +++ b/EXILED/Exiled.API/Features/Warhead.cs @@ -127,6 +127,11 @@ public static WarheadStatus Status /// public static bool IsInProgress => Controller.Info.InProgress; + /// + /// Gets a value indicating whether the warhead detonation is on cooldown. + /// + public static bool IsOnCooldown => Controller.CooldownEndTime > NetworkTime.time; + /// /// Gets or sets the warhead detonation timer. /// @@ -162,7 +167,7 @@ public static int Kills /// /// Gets a value indicating whether the warhead can be started. /// - public static bool CanBeStarted => !IsInProgress && !IsDetonated && Controller.CooldownEndTime <= NetworkTime.time; + public static bool CanBeStarted => !IsInProgress && !IsDetonated && !IsOnCooldown; /// /// Closes the surface blast doors. From bec65a3de67671d95cd586183112b3e8df9cfce2 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:16:46 +0100 Subject: [PATCH 12/33] AllowsScp106 setter use C# 14 --- EXILED/Exiled.API/Features/Doors/Door.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 81dcb427f4..e1e09eb659 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -203,11 +203,7 @@ public Vector3 Position public bool AllowsScp106 { get => Base is IScp106PassableDoor door && door.IsScp106Passable; - set - { - if (Base is IScp106PassableDoor door) - door.IsScp106Passable = value; - } + set => (Base as IScp106PassableDoor)?.IsScp106Passable = value; } /// From 41e9132874354b3533c9cb6595f632d6a975df17 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:17:54 +0100 Subject: [PATCH 13/33] fix: `Door.AllowsScp106` --- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index e1e09eb659..09865ab206 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -202,7 +202,7 @@ public Vector3 Position /// public bool AllowsScp106 { - get => Base is IScp106PassableDoor door && door.IsScp106Passable; + get => Base is not IScp106PassableDoor door || door.IsScp106Passable; set => (Base as IScp106PassableDoor)?.IsScp106Passable = value; } From 0b968a75633720f7d640c4ebbe4bd1deb65f7090 Mon Sep 17 00:00:00 2001 From: Banalny-Banan <133122450+Banalny-Banan@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:07:21 +0200 Subject: [PATCH 14/33] fix: AdminToy.Position & Rotation ignoring parenting (#687) fix AdminToy.Position & Rotation ignoring parenting --- EXILED/Exiled.API/Features/Toys/AdminToy.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.API/Features/Toys/AdminToy.cs b/EXILED/Exiled.API/Features/Toys/AdminToy.cs index c93cee110e..15cbc0ab72 100644 --- a/EXILED/Exiled.API/Features/Toys/AdminToy.cs +++ b/EXILED/Exiled.API/Features/Toys/AdminToy.cs @@ -79,11 +79,11 @@ public Footprint Footprint /// public Vector3 Position { - get => AdminToyBase.transform.position; + get => Transform.position; set { - AdminToyBase.transform.position = value; - AdminToyBase.NetworkPosition = value; + Transform.position = value; + AdminToyBase.NetworkPosition = Transform.localPosition; } } @@ -92,11 +92,11 @@ public Vector3 Position /// public Quaternion Rotation { - get => AdminToyBase.transform.rotation; + get => Transform.rotation; set { - AdminToyBase.transform.rotation = value; - AdminToyBase.NetworkRotation = value; + Transform.rotation = value; + AdminToyBase.NetworkRotation = Transform.localRotation; } } @@ -105,10 +105,10 @@ public Quaternion Rotation /// public Vector3 Scale { - get => AdminToyBase.transform.localScale; + get => Transform.localScale; set { - AdminToyBase.transform.localScale = value; + Transform.localScale = value; AdminToyBase.NetworkScale = value; } } From 3eea13e49b1e97051cde8bf9efb9b54a23b5a220 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Wed, 18 Mar 2026 19:38:47 +0100 Subject: [PATCH 15/33] nothing: Is this check better ? (#777) is it better ? --- EXILED/Exiled.API/Features/Doors/Door.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 09865ab206..960c9c48c0 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -638,7 +638,7 @@ private DoorType GetDoorType() }, "Cargo Elevator Door" => DoorType.ElevatorServerRoom, "Nuke Elevator Door" => DoorType.ElevatorNuke, - "Elevator Door" or "Elevator Door 02" or "Elevator Door 01" => (Base as Interactables.Interobjects.ElevatorDoor)?.Group switch + not null when Base is Interactables.Interobjects.ElevatorDoor elevatorGroup => elevatorGroup?.Group switch { ElevatorGroup.Scp049 => DoorType.ElevatorScp049, ElevatorGroup.GateB => DoorType.ElevatorGateB, From d82093d3b80b1aa377fd8918163b385450377aab Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:39:53 +0300 Subject: [PATCH 16/33] fix: Replace new Locker instance creation with Get method (#779) Replace new Locker instance creation with Get method --- EXILED/Exiled.Events/Patches/Generic/LockerList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs index f76ac47c51..b0bd8af1de 100644 --- a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs @@ -40,7 +40,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } -} \ No newline at end of file +} From 053e328c51c62666069b19e4adb1245b379c6527 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:40:12 +0300 Subject: [PATCH 17/33] feat: Add New Gate Type & Fix Some door not Gate (#776) * f'x * Update DoorTypeExtensions.cs * ordering --- EXILED/Exiled.API/Enums/DoorType.cs | 5 +++++ EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs | 2 +- EXILED/Exiled.API/Features/Doors/Door.cs | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/DoorType.cs b/EXILED/Exiled.API/Enums/DoorType.cs index 5863d6e79a..5e99c32a22 100644 --- a/EXILED/Exiled.API/Enums/DoorType.cs +++ b/EXILED/Exiled.API/Enums/DoorType.cs @@ -374,5 +374,10 @@ public enum DoorType /// Represents the door in . /// HczLoadingBay, + + /// + /// Represents a spawnable unsecured gate. + /// + SpawnableUnsecuredGate, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs index a4380d81cf..1124771480 100644 --- a/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DoorTypeExtensions.cs @@ -20,7 +20,7 @@ public static class DoorTypeExtensions /// The door to be checked. /// Returns whether the is a gate. public static bool IsGate(this DoorType door) => door is DoorType.GateA or DoorType.GateB or DoorType.Scp914Gate or - DoorType.Scp049Gate or DoorType.GR18Gate or DoorType.SurfaceGate or DoorType.Scp173Gate; + DoorType.Scp049Gate or DoorType.GR18Gate or DoorType.SurfaceGate or DoorType.Scp173Gate or DoorType.Scp173NewGate or DoorType.CheckpointGateA or DoorType.CheckpointGateB or DoorType.SpawnableUnsecuredGate or DoorType.UnknownGate; /// /// Checks if a door type is a checkpoint. diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index 960c9c48c0..0e27b6daae 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -647,6 +647,7 @@ private DoorType GetDoorType() ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => DoorType.ElevatorLczB, _ => DoorType.UnknownElevator, }, + "Spawnable Unsecured Pryable GateDoor" => DoorType.SpawnableUnsecuredGate, _ => DoorType.UnknownDoor, }; } From eca6029bd4834004bd59eb52ae0a52d70d5f4a47 Mon Sep 17 00:00:00 2001 From: Unbistrackted <112902220+Unbistrackted@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:03:11 -0300 Subject: [PATCH 18/33] feat(warhead)!: ``Warhead.RemainingCooldown`` and ``WarheadStatus.OnCooldown`` (#759) * feat(warhead): Add ``IsOnCooldown`` property (#757) * feat(warhead): Add WarheadStatus.OnCooldown * feat(warhead): Add ``RemainingCooldown`` property Change from ternary operators for readability Add case for ``WarheadStatus.OnCooldown`` * fix(warhead): Use ``Warhead.RemainingCooldown`` instead * feat(warhead): ``WarheadStatus`` as Flag As per @louis1706 recommendation * fix(warhead): Add the byte values cause my visual studio didn't saved * fix(warhead): change status setter to use flags * fix(warhead): add more logic to the nuke status setter * fix: Build Error --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Enums/WarheadStatus.cs | 16 ++++-- EXILED/Exiled.API/Features/Warhead.cs | 67 +++++++++++++++++------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/EXILED/Exiled.API/Enums/WarheadStatus.cs b/EXILED/Exiled.API/Enums/WarheadStatus.cs index e839c0b8dc..ed6f7d5a6d 100644 --- a/EXILED/Exiled.API/Enums/WarheadStatus.cs +++ b/EXILED/Exiled.API/Enums/WarheadStatus.cs @@ -7,30 +7,38 @@ namespace Exiled.API.Enums { + using System; + /// /// All the available warhead statuses. /// /// + [Flags] public enum WarheadStatus { /// /// The warhead is not armed. /// - NotArmed, + NotArmed = 0, /// /// The warhead is armed. /// - Armed, + Armed = 1, /// /// The warhead detonation is in progress. /// - InProgress, + InProgress = 2, /// /// The warhead has detonated. /// - Detonated, + Detonated = 4, + + /// + /// The warhead is on cooldown. + /// + OnCooldown = 8, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Warhead.cs b/EXILED/Exiled.API/Features/Warhead.cs index d26cf33e84..f765905410 100644 --- a/EXILED/Exiled.API/Features/Warhead.cs +++ b/EXILED/Exiled.API/Features/Warhead.cs @@ -7,12 +7,13 @@ namespace Exiled.API.Features { + using System; using System.Collections.Generic; using Enums; + using Exiled.API.Extensions; using Interactables.Interobjects.DoorUtils; using Mirror; - using UnityEngine; /// @@ -33,7 +34,7 @@ public static class Warhead /// /// Gets the cached component. /// - public static AlphaWarheadOutsitePanel OutsitePanel => field != null ? field : (field = Object.FindFirstObjectByType()); + public static AlphaWarheadOutsitePanel OutsitePanel => field != null ? field : (field = UnityEngine.Object.FindFirstObjectByType()); /// /// Gets the of the warhead lever. @@ -67,6 +68,15 @@ public static bool OpenDoors set => Controller._openDoors = value; } + /// + /// Gets or sets the remaining cooldown before the nuke can be triggered again. + /// + public static double RemainingCooldown + { + get => Math.Max(0, Controller.NetworkCooldownEndTime - NetworkTime.time); + set => Controller.NetworkCooldownEndTime = NetworkTime.time + Math.Max(0, value); + } + /// /// Gets all of the warhead blast doors. /// @@ -95,25 +105,42 @@ public static bool IsKeycardActivated /// public static WarheadStatus Status { - get => IsInProgress ? IsDetonated ? WarheadStatus.Detonated : WarheadStatus.InProgress : LeverStatus ? WarheadStatus.Armed : WarheadStatus.NotArmed; + get + { + WarheadStatus status = WarheadStatus.NotArmed; + + if (IsDetonated) + status |= WarheadStatus.Detonated; + + if (IsInProgress) + status |= WarheadStatus.InProgress; + + if (IsOnCooldown) + status |= WarheadStatus.OnCooldown; + + if (LeverStatus) + status |= WarheadStatus.Armed; + + return status; + } + set { - switch (value) - { - case WarheadStatus.NotArmed: - case WarheadStatus.Armed: - Stop(); - LeverStatus = value is WarheadStatus.Armed; - break; - - case WarheadStatus.InProgress: - Start(); - break; - - case WarheadStatus.Detonated: - Detonate(); - break; - } + if (IsDetonated) + return; + + LeverStatus = value.HasFlagFast(WarheadStatus.Armed); + + if (!IsInProgress && value.HasFlagFast(WarheadStatus.InProgress)) + Start(); + else if (!value.HasFlagFast(WarheadStatus.InProgress)) + Stop(); + + if (value.HasFlagFast(WarheadStatus.Detonated)) + Detonate(); + + if (!IsOnCooldown && value.HasFlagFast(WarheadStatus.OnCooldown)) + RemainingCooldown = Controller._cooldown; } } @@ -130,7 +157,7 @@ public static WarheadStatus Status /// /// Gets a value indicating whether the warhead detonation is on cooldown. /// - public static bool IsOnCooldown => Controller.CooldownEndTime > NetworkTime.time; + public static bool IsOnCooldown => RemainingCooldown > 0; /// /// Gets or sets the warhead detonation timer. From 63c4f69dd90deba86a20039cce22aa76e50ab726 Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:07:31 +0100 Subject: [PATCH 19/33] fix: PrefabHelper HczOneSided / HczTwoSided / HczBreakableDoor (#767) feat: PrefabHelper HczOneSided / HczTwoSided / HczBreakableDoor --- EXILED/Exiled.API/Enums/PrefabType.cs | 1 + EXILED/Exiled.API/Features/PrefabHelper.cs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs index 43936f1d80..c3ce00898d 100644 --- a/EXILED/Exiled.API/Enums/PrefabType.cs +++ b/EXILED/Exiled.API/Enums/PrefabType.cs @@ -12,6 +12,7 @@ namespace Exiled.API.Enums /// /// Type of prefab. /// + /// public enum PrefabType { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member diff --git a/EXILED/Exiled.API/Features/PrefabHelper.cs b/EXILED/Exiled.API/Features/PrefabHelper.cs index 179991e635..22579651ac 100644 --- a/EXILED/Exiled.API/Features/PrefabHelper.cs +++ b/EXILED/Exiled.API/Features/PrefabHelper.cs @@ -14,8 +14,8 @@ namespace Exiled.API.Features using Exiled.API.Enums; using Exiled.API.Features.Attributes; - using MapGeneration.Distributors; + using MapGeneration.RoomConnectors; using Mirror; using UnityEngine; @@ -57,6 +57,11 @@ public static PrefabAttribute GetPrefabAttribute(this PrefabType prefabType) /// Returns the . public static GameObject GetPrefab(PrefabType prefabType) { + if (prefabType is PrefabType.HCZOneSided or PrefabType.HCZTwoSided) + { + prefabType = PrefabType.HCZBreakableDoor; + } + if (Prefabs.TryGetValue(prefabType, out (GameObject, Component) prefab)) return prefab.Item1; @@ -112,6 +117,17 @@ public static GameObject Spawn(PrefabType prefabType, Vector3 position = default positionSync.Network_rotationY = (sbyte)Mathf.RoundToInt(rotation.Value.eulerAngles.y / 5.625F); } + if (prefabType is PrefabType.HCZOneSided or PrefabType.HCZTwoSided or PrefabType.HCZBreakableDoor) + { + newGameObject.GetComponent().Network_syncBitmask = prefabType switch + { + PrefabType.HCZTwoSided => 0b00000000, + PrefabType.HCZOneSided => 0b00000001, + PrefabType.HCZBreakableDoor => 0b00000011, + _ => 0 + }; + } + NetworkServer.Spawn(newGameObject); return newGameObject; From 8f544a0f6e588ce045ece1c21f54d8e9ddc9d0bd Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:10:07 +0300 Subject: [PATCH 20/33] fix: Item leaks (#763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix major memory leaks in Items and optimize custom item spawning * fix armors to * more * ,. * f'x f'x * ö * , * someonefix * fix someone * someonesafe * somefix * Update EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Update EXILED/Exiled.API/Features/Player.cs Yeah right Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * fix: naming broke because of Yamato Code Review --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- .../DamageHandlers/GenericDamageHandler.cs | 86 ++++++++++++------- EXILED/Exiled.API/Features/Items/Item.cs | 14 ++- EXILED/Exiled.API/Features/Player.cs | 15 +++- .../API/Features/CustomArmor.cs | 2 +- .../API/Features/CustomGrenade.cs | 36 +++++--- .../API/Features/CustomItem.cs | 3 +- .../API/Features/CustomWeapon.cs | 23 +++-- .../Patches/Generic/StaminaRegenArmor.cs | 7 +- 8 files changed, 132 insertions(+), 54 deletions(-) diff --git a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs index d55324798e..4ea1909b72 100644 --- a/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs +++ b/EXILED/Exiled.API/Features/DamageHandlers/GenericDamageHandler.cs @@ -10,19 +10,33 @@ namespace Exiled.API.Features.DamageHandlers using System; using Enums; + + using Exiled.API.Extensions; using Exiled.API.Features.Pickups.Projectiles; using Footprinting; + + using InventorySystem; + using InventorySystem.Items; + using InventorySystem.Items.Firearms; + using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Firearms.ShotEvents; using InventorySystem.Items.Scp1509; + using Items; + using PlayerRoles; using PlayerRoles.PlayableScps.Scp096; using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.PlayableScps.Scp3114; using PlayerRoles.PlayableScps.Scp939; + using PlayerStatsSystem; + using UnityEngine; + using Object = UnityEngine.Object; + /// /// Allows generic damage to a player. /// @@ -59,7 +73,7 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage if (customCassieAnnouncement is not null) customCassieAnnouncement.Announcement ??= $"{player.Nickname} killed by {attacker.Nickname} utilizing {damageType}"; - Attacker = attacker.Footprint; + Attacker = attacker != null ? attacker.Footprint : Server.Host.Footprint; AllowSelfDamage = true; Damage = damage; ServerLogsText = $"GenericDamageHandler damage processing"; @@ -123,55 +137,57 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage Base = new GrayCandyDamageHandler(Attacker.Hub, damage); break; case DamageType.MicroHid: - InventorySystem.Items.MicroHID.MicroHIDItem microHidOwner = new(); - microHidOwner.Owner = attacker.ReferenceHub; + InventorySystem.Items.MicroHID.MicroHIDItem microHidOwner = new() + { + Owner = attacker.ReferenceHub, + }; Base = new MicroHidDamageHandler(damage, microHidOwner); break; case DamageType.Explosion: - Base = new ExplosionDamageHandler(attacker.Footprint, UnityEngine.Vector3.zero, damage, 0, ExplosionType.Grenade); + Base = new ExplosionDamageHandler(attacker.Footprint, Vector3.zero, damage, 0, ExplosionType.Grenade); break; case DamageType.Firearm: case DamageType.AK: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunAK); + GenericFirearm(damage, ItemType.GunAK); break; case DamageType.Crossvec: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunCrossvec); + GenericFirearm(damage, ItemType.GunCrossvec); break; case DamageType.Logicer: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunLogicer); + GenericFirearm(damage, ItemType.GunLogicer); break; case DamageType.Revolver: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunRevolver); + GenericFirearm(damage, ItemType.GunRevolver); break; case DamageType.Shotgun: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunShotgun); + GenericFirearm(damage, ItemType.GunShotgun); break; case DamageType.Com15: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunCOM15); + GenericFirearm(damage, ItemType.GunCOM15); break; case DamageType.Com18: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunCOM18); + GenericFirearm(damage, ItemType.GunCOM18); break; case DamageType.Fsp9: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunFSP9); + GenericFirearm(damage, ItemType.GunFSP9); break; case DamageType.E11Sr: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunE11SR); + GenericFirearm(damage, ItemType.GunE11SR); break; case DamageType.Com45: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunCom45); + GenericFirearm(damage, ItemType.GunCom45); break; case DamageType.Frmg0: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunFRMG0); + GenericFirearm(damage, ItemType.GunFRMG0); break; case DamageType.A7: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunA7); + GenericFirearm(damage, ItemType.GunA7); break; case DamageType.Scp127: - GenericFirearm(player, attacker, damage, damageType, ItemType.GunSCP127); + GenericFirearm(damage, ItemType.GunSCP127); break; case DamageType.ParticleDisruptor: - Base = new DisruptorDamageHandler(new (Item.Create(ItemType.ParticleDisruptor, attacker).Base as InventorySystem.Items.Firearms.Firearm, InventorySystem.Items.Firearms.Modules.DisruptorActionModule.FiringState.FiringSingle), Vector3.up, damage); + Base = new DisruptorDamageHandler(new DisruptorShotEvent(default, Attacker, InventorySystem.Items.Firearms.Modules.DisruptorActionModule.FiringState.FiringSingle), Vector3.up, damage); break; case DamageType.Scp096: Scp096Role curr096 = attacker.ReferenceHub.roleManager.CurrentRole as Scp096Role ?? new Scp096Role(); @@ -193,9 +209,12 @@ public GenericDamageHandler(Player player, Player attacker, float damage, Damage Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Unknown); break; case DamageType.Scp018: - Scp018Projectile scp018Projectile = Projectile.Create(ProjectileType.Scp018); - scp018Projectile.PreviousOwner = attacker; - Base = new Scp018DamageHandler(scp018Projectile.Base, damage, true); + InventorySystem.Items.ThrowableProjectiles.Scp018Projectile dummy018 = new() + { + PreviousOwner = Attacker, + }; + + Base = new Scp018DamageHandler(dummy018, damage, true); break; case DamageType.Scp207: Base = new PlayerStatsSystem.ScpDamageHandler(attacker.ReferenceHub, damage, DeathTranslations.Scp207); @@ -303,21 +322,26 @@ public override HandlerOutput ApplyDamage(ReferenceHub ply) /// /// Generic firearm path for handle type. /// - /// Current player. - /// Current attacker. /// Damage amount. - /// Damage type. /// ItemType. - private void GenericFirearm(Player player, Player attacker, float amount, DamageType damageType, ItemType itemType) + private void GenericFirearm(float amount, ItemType itemType) { - Firearm firearm = new(itemType) + ItemType ammoType = ItemType.None; + + if (InventoryItemLoader.TryGetItem(itemType, out InventorySystem.Items.Firearms.Firearm firearmTemplate)) { - Base = - { - Owner = attacker.ReferenceHub, - }, + Items.Firearm firearm = new(firearmTemplate); + ammoType = firearm.AmmoType.GetItemType(); + } + + Base = new PlayerStatsSystem.FirearmDamageHandler + { + Damage = amount, + Attacker = Attacker, + AmmoType = ammoType, + WeaponType = itemType, + Firearm = firearmTemplate, }; - Base = new PlayerStatsSystem.FirearmDamageHandler() { Firearm = firearm.Base, Damage = amount }; } } } diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 89bb189c42..6e9a20d66f 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -414,7 +414,19 @@ public static T Create(ItemType type, Player owner = null) /// /// Destroy this item. /// - public void Destroy() => Owner.RemoveItem(this); + public void Destroy() + { + if (Owner.RemoveItem(this)) + return; + + if (Base != null) + { + BaseToItem.Remove(Base); + + if (Base.gameObject != null) + Object.Destroy(Base.gameObject); + } + } /// /// Creates the that based on this . diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 6f4f500e65..88c0693232 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -2324,7 +2324,17 @@ public void Heal(float amount, bool overrideMaxHealth = false) /// /// The ItemType to be used. /// if item was used successfully. Otherwise, . - public bool UseItem(ItemType usableItem) => UseItem(Item.Create(usableItem)); + public bool UseItem(ItemType usableItem) + { + if (usableItem.GetTemplate() is not UsableItem) + return false; + + Item usable = Item.Create(usableItem); + + UseItem(usable); + usable.Destroy(); + return true; + } /// /// Forces the player to use an item. @@ -2388,7 +2398,8 @@ public void Vaporize(Player attacker = null, string cassieAnnouncement = "") if ((Role.Side != Side.Scp) && !string.IsNullOrEmpty(cassieAnnouncement)) Cassie.Message(cassieAnnouncement); - Kill(new DisruptorDamageHandler(new DisruptorShotEvent(Item.Create(ItemType.ParticleDisruptor, attacker).Base as InventorySystem.Items.Firearms.Firearm, DisruptorActionModule.FiringState.FiringSingle), Vector3.up, -1)); + Footprint footprint = attacker != null ? attacker.Footprint : Server.Host.Footprint; + Kill(new DisruptorDamageHandler(new DisruptorShotEvent(default, footprint, DisruptorActionModule.FiringState.FiringSingle), Vector3.up, -1)); } /// diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs b/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs index e6f1932905..c1a77f6351 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomArmor.cs @@ -82,7 +82,7 @@ public override void Give(Player player, bool displayMessage = true) if (AmmoLimits.Count != 0) armor.AmmoLimits = AmmoLimits; - if (AmmoLimits.Count != 0) + if (CategoryLimits.Count != 0) armor.CategoryLimits = CategoryLimits; player.AddItem(armor); diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomGrenade.cs b/EXILED/Exiled.CustomItems/API/Features/CustomGrenade.cs index 48d725ce7e..feafdae9c2 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomGrenade.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomGrenade.cs @@ -19,10 +19,14 @@ namespace Exiled.CustomItems.API.Features using Exiled.Events.EventArgs.Player; using Footprinting; + + using InventorySystem; using InventorySystem.Items; using InventorySystem.Items.Pickups; using InventorySystem.Items.ThrowableProjectiles; + using Mirror; + using UnityEngine; using Object = UnityEngine.Object; @@ -70,31 +74,43 @@ public override ItemType Type /// The spawned. public virtual Pickup Throw(Vector3 position, float force, float weight, float fuseTime = 3f, ItemType grenadeType = ItemType.GrenadeHE, Player? player = null) { - if (player is null) - player = Server.Host; + player ??= Server.Host; - player.Role.Is(out FpcRole fpcRole); - Vector3 velocity = fpcRole.FirstPersonController.FpcModule.Motor.Velocity; + Vector3 velocity = Vector3.zero; + Quaternion rotation = Quaternion.identity; - Throwable throwable = (Throwable)Item.Create(grenadeType, player); + if (player != Server.Host) + { + if (player.Role.Is(out FpcRole fpcRole)) + velocity = fpcRole.FirstPersonController.FpcModule.Motor.Velocity; + + if (player.CameraTransform != null) + rotation = player.CameraTransform.rotation; + } - ThrownProjectile thrownProjectile = Object.Instantiate(throwable.Base.Projectile, position, throwable.Owner.CameraTransform.rotation); + InventoryItemLoader.TryGetItem(grenadeType, out ThrowableItem template); + + ThrownProjectile thrownProjectile = Object.Instantiate(template.Projectile, position, rotation); PickupSyncInfo newInfo = new() { - ItemId = throwable.Type, - Locked = !throwable.Base._repickupable, + ItemId = grenadeType, + Locked = !template._repickupable, Serial = ItemSerialGenerator.GenerateNext(), WeightKg = weight, }; + if (thrownProjectile is TimeGrenade time) time._fuseTime = fuseTime; + thrownProjectile.NetworkInfo = newInfo; - thrownProjectile.PreviousOwner = new Footprint(throwable.Owner.ReferenceHub); + thrownProjectile.PreviousOwner = player.Footprint; NetworkServer.Spawn(thrownProjectile.gameObject); thrownProjectile.InfoReceivedHook(default, newInfo); + if (thrownProjectile.TryGetComponent(out Rigidbody component)) - throwable.Base.PropelBody(component, throwable.Base.FullThrowSettings.StartTorque, ThrowableNetworkHandler.GetLimitedVelocity(velocity)); + template.PropelBody(component, template.FullThrowSettings.StartTorque, ThrowableNetworkHandler.GetLimitedVelocity(velocity)); + thrownProjectile.ServerActivate(); return Pickup.Get(thrownProjectile); diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs index 789fd25ceb..e131db7086 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs @@ -540,7 +540,7 @@ public static IEnumerable UnregisterItems(IEnumerable targetTy Pickup? pickup = Spawn(position, item, previousOwner); - UnityEngine.Object.Destroy(item.Base); + item.Destroy(); return pickup; } @@ -554,6 +554,7 @@ public static IEnumerable UnregisterItems(IEnumerable targetTy public virtual Pickup? Spawn(Vector3 position, Item item, Player? previousOwner = null) { Pickup? pickup = item.CreatePickup(position); + pickup.Scale = Scale; pickup.Weight = Weight; diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs index 9053cf2d66..cc2e7f25bc 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomWeapon.cs @@ -18,9 +18,11 @@ namespace Exiled.CustomItems.API.Features using Exiled.API.Features.Pickups; using Exiled.Events.EventArgs.Item; using Exiled.Events.EventArgs.Player; + using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; + using UnityEngine; using Firearm = Exiled.API.Features.Items.Firearm; @@ -69,16 +71,22 @@ public override ItemType Type /// public override Pickup? Spawn(Vector3 position, Player? previousOwner = null) { - if (Item.Create(Type) is not Firearm firearm) + if (Type.IsWeapon(false)) { - Log.Debug($"{nameof(Spawn)}: Item is not Firearm."); + Log.Warn($"{nameof(Spawn)}: Item is not Firearm."); return null; } + Firearm firearm = Item.Create(Type); + if (!Attachments.IsEmpty()) firearm.AddAttachment(Attachments); - Pickup? pickup = firearm.CreatePickup(position); + if (ClipSize > 0) + firearm.MagazineAmmo = ClipSize; + + FirearmPickup? pickup = (FirearmPickup?)firearm.CreatePickup(position, spawn: false); + firearm.Destroy(); if (pickup is null) { @@ -86,15 +94,16 @@ public override ItemType Type return null; } - if (ClipSize > 0) - firearm.MagazineAmmo = ClipSize; - pickup.Weight = Weight; pickup.Scale = Scale; + if (previousOwner is not null) pickup.PreviousOwner = previousOwner; + pickup.Spawn(); + TrackedSerials.Add(pickup.Serial); + return pickup; } @@ -108,9 +117,11 @@ public override ItemType Type if (ClipSize > 0) firearm.MagazineAmmo = ClipSize; + int ammo = firearm.MagazineAmmo; Log.Debug($"{nameof(Name)}.{nameof(Spawn)}: Spawning weapon with {ammo} ammo."); Pickup? pickup = firearm.CreatePickup(position); + pickup.Scale = Scale; if (previousOwner is not null) diff --git a/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs b/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs index 96441e17e9..38ad7536e2 100644 --- a/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs +++ b/EXILED/Exiled.Events/Patches/Generic/StaminaRegenArmor.cs @@ -16,14 +16,17 @@ namespace Exiled.Events.Patches.Generic /// /// Patches . - /// Implements . + /// 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) + if (__instance.ItemSerial == 0) + return; + + if (Item.Get(__instance) is Armor armor) __result *= armor.StaminaRegenMultiplier; } } From 1cf9683468ab3a0cd2626d30eac9a146a7a57255 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sun, 29 Mar 2026 05:27:10 +0300 Subject: [PATCH 21/33] fix: SavingByAntiScp207 refactor the current game code & event not saving (#771) * fix & refactor * removed denied damage multiplayer prop * hj * cmmon get fixed git --- .../Player/SavingByAntiScp207EventArgs.cs | 13 +++-------- .../Events/Player/SavingByAntiScp207.cs | 22 +++++++++---------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs index 71cd25905b..47b568d667 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/SavingByAntiScp207EventArgs.cs @@ -31,8 +31,6 @@ public SavingByAntiScp207EventArgs(ReferenceHub player, float damageAmount, Dama Handler = handler; HitboxType = hitboxType; DamageAmount = damageAmount; - DamageMultiplier = (Player.Health + Player.ArtificialHealth - AntiScp207.DeathSaveHealth) / damageAmount; - IsAllowed = true; } /// @@ -46,14 +44,9 @@ public SavingByAntiScp207EventArgs(ReferenceHub player, float damageAmount, Dama public float DamageAmount { get; } /// - /// Gets or sets the multiplier for the damage that is applied when the event is allowed. + /// Gets or sets the health amount the player will have after being saved from death. /// - public float DamageMultiplier { get; set; } - - /// - /// Gets or sets the multiplier for the damage that if event denied. - /// - public float DeniedDamageMultiplier { get; set; } = 1; + public float DeathSaveHealth { get; set; } = AntiScp207.DeathSaveHealth; /// /// Gets the damage handler that describes the incoming damage. @@ -69,6 +62,6 @@ public SavingByAntiScp207EventArgs(ReferenceHub player, float damageAmount, Dama /// Gets or sets a value indicating whether the event is allowed. /// If set to false, the event will be denied. /// - public bool IsAllowed { get; set; } + public bool IsAllowed { get; set; } = true; } } diff --git a/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs index ac4157b7ce..daa9f0c6fb 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/SavingByAntiScp207.cs @@ -31,19 +31,19 @@ private static IEnumerable Transpiler(IEnumerable newInstructions = ListPool.Pool.Get(instructions); + Label skipLabel = generator.DefineLabel(); LocalBuilder ev = generator.DeclareLocal(typeof(SavingByAntiScp207EventArgs)); int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldloc_1); - Label skipLabel = generator.DefineLabel(); - Label gotoEventLabel = newInstructions[index].labels[0]; + List