From efebf223e8f0db7b543d93425d94fc0d4ae7a771 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Fri, 2 Jan 2026 22:54:10 +0300
Subject: [PATCH 01/38] Speaker Toy Api
---
EXILED/Exiled.API/Enums/SpeakerPlayMode.cs | 30 ++
.../Features/Audio/PreloadedPcmSource.cs | 68 +++++
.../Features/Audio/WavStreamSource.cs | 77 +++++
EXILED/Exiled.API/Features/Toys/Speaker.cs | 281 +++++++++++++++++-
EXILED/Exiled.API/Interfaces/IPcmSource.cs | 36 +++
5 files changed, 486 insertions(+), 6 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/Interfaces/IPcmSource.cs
diff --git a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
new file mode 100644
index 0000000000..8ab0a46117
--- /dev/null
+++ b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
@@ -0,0 +1,30 @@
+// -----------------------------------------------------------------------
+//
+// 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
+ {
+ ///
+ /// Play audio globally to all players.
+ ///
+ Global,
+
+ ///
+ /// Play audio to a specific list of players.
+ ///
+ PlayerList,
+
+ ///
+ /// Play audio to players matching a predicate.
+ ///
+ Predicate,
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
new file mode 100644
index 0000000000..3e181704b9
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -0,0 +1,68 @@
+// -----------------------------------------------------------------------
+//
+// 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;
+
+ ///
+ /// Represents a preloaded PCM audio source.
+ ///
+ public 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 PCM data to preload.
+ public PreloadedPcmSource(float[] pcm)
+ {
+ data = pcm;
+ }
+
+ ///
+ public bool Ended
+ {
+ get
+ {
+ return pos >= data.Length;
+ }
+ }
+
+ ///
+ 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;
+ }
+
+ ///
+ public void Reset()
+ {
+ pos = 0;
+ }
+
+ ///
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
new file mode 100644
index 0000000000..01f3944297
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -0,0 +1,77 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Audio
+{
+ using System.IO;
+
+ using Exiled.API.Features.Toys;
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Provides a PCM audio source from a WAV file stream.
+ ///
+ public class WavStreamSource : IPcmSource
+ {
+ private readonly long endPosition;
+ private readonly long startPosition;
+ private readonly BinaryReader reader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The path to the audio file.
+ public WavStreamSource(string path)
+ {
+ reader = new BinaryReader(File.OpenRead(path));
+ Speaker.SkipWavHeader(reader);
+ startPosition = reader.BaseStream.Position;
+ endPosition = reader.BaseStream.Length;
+ }
+
+ ///
+ /// Gets a value indicating whether the end of the stream has been reached.
+ ///
+ public bool Ended
+ {
+ get
+ {
+ return reader.BaseStream.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 i = 0;
+
+ while (i < count && reader.BaseStream.Position < endPosition)
+ {
+ buffer[offset + i] = reader.ReadInt16() / 32768f;
+ i++;
+ }
+
+ return i;
+ }
+
+ ///
+ /// Resets the stream position to the start.
+ ///
+ public void Reset() => reader.BaseStream.Position = startPosition;
+
+ ///
+ /// Releases all resources used by the .
+ ///
+ public void Dispose() => reader.Dispose();
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 8b0af6bd79..45d736a98e 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -7,26 +7,60 @@
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 .
+ /// Represents a speaker toy that can play audio in the game world.
+ /// Provides methods for playing, stopping, and controlling audio playback, as well as managing playback settings.
///
public class Speaker : AdminToy, IWrapper
{
+ private const int SampleRate = VoiceChatSettings.SampleRate;
+ private const int FrameSize = VoiceChatSettings.PacketSizePerChannel;
+ private const float FrameTime = (float)FrameSize / SampleRate;
+
+ private readonly OpusEncoder encoder;
+ private readonly float[] frame = new float[FrameSize];
+ private readonly byte[] encoded = new byte[VoiceChatSettings.MaxEncodedSize];
+
+ private IPcmSource source;
+ private float timeAccumulator;
+ private CoroutineHandle playBackRoutine;
+
///
/// Initializes a new instance of the class.
///
/// The of the toy.
internal Speaker(SpeakerToy speakerToy)
- : base(speakerToy, AdminToyType.Speaker) => Base = speakerToy;
+ : base(speakerToy, AdminToyType.Speaker)
+ {
+ Base = speakerToy;
+ encoder = new OpusEncoder(OpusApplicationType.Audio);
+ AdminToyBase.OnRemoved += OnToyRemoved;
+ }
///
/// Gets the prefab.
@@ -38,6 +72,57 @@ 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; }
+
+ ///
+ /// 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 list of target players who will hear the audio played by this speaker when is set to .
+ ///
+ public List 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
+ {
+ get => 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 => playBackRoutine.IsAliveAndPaused = value;
+ }
+
///
/// Gets or sets the volume of the audio source.
///
@@ -109,7 +194,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 +223,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 +247,189 @@ 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 PlayWav(string path, bool stream = true, bool destroyAfter = false, bool loop = false)
+ {
+ Stop();
+
+ Loop = loop;
+ DestroyAfter = destroyAfter;
+ source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(WavToPcm(path));
+ playBackRoutine = Timing.RunCoroutine(PlayBackCoroutine().CancelWith(GameObject));
+ }
+
+ ///
+ /// Stops playback.
+ ///
+ public void Stop()
+ {
+ if (playBackRoutine.IsRunning)
+ Timing.KillCoroutines(playBackRoutine);
+
+ source?.Dispose();
+ source = null;
+ }
+
+ ///
+ /// Skips the WAV header.
+ ///
+ /// The binary reader.
+ internal static void SkipWavHeader(BinaryReader br)
+ {
+ br.ReadBytes(12);
+
+ while (true)
+ {
+ string chunk = new(br.ReadChars(4));
+ int size = br.ReadInt32();
+
+ if (chunk == "fmt ")
+ {
+ short format = br.ReadInt16();
+ short channels = br.ReadInt16();
+ int rate = br.ReadInt32();
+ br.ReadInt32();
+ br.ReadInt16();
+ short bits = br.ReadInt16();
+
+ if (format != 1 || channels != 1 || rate != SampleRate || bits != 16)
+ Log.Error("WAV must be PCM16 mono 48kHz");
+
+ br.BaseStream.Position += size - 16;
+ }
+ else if (chunk == "data")
+ {
+ return;
+ }
+ else
+ {
+ br.BaseStream.Position += size;
+ }
+ }
+ }
+
+ private IEnumerator PlayBackCoroutine()
+ {
+ timeAccumulator = 0f;
+
+ while (true)
+ {
+ timeAccumulator += Time.deltaTime;
+
+ while (timeAccumulator >= FrameTime)
+ {
+ timeAccumulator -= FrameTime;
+
+ int read = source.Read(frame, 0, FrameSize);
+
+ if (read < FrameSize)
+ Array.Clear(frame, read, FrameSize - read);
+
+ int len = encoder.Encode(frame, encoded);
+
+ if (len > 2)
+ SendPacket(len);
+
+ if (!source.Ended)
+ continue;
+
+ if (Loop)
+ {
+ source.Reset();
+ timeAccumulator = 0f;
+ break;
+ }
+
+ if (DestroyAfter)
+ {
+ NetworkServer.Destroy(GameObject);
+ }
+
+ yield break;
+ }
+
+ yield return Timing.WaitForOneFrame;
+ }
+ }
+
+ private void SendPacket(int len)
+ {
+ AudioMessage msg = new(ControllerId, encoded, len);
+
+ switch(PlayMode)
+ {
+ case SpeakerPlayMode.Global:
+ NetworkServer.SendToReady(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 writer2 = NetworkWriterPool.Get())
+ {
+ NetworkMessages.Pack(msg, writer2);
+ ArraySegment segment = writer2.ToArraySegment();
+
+ foreach (Player ply in Player.List)
+ {
+ if (Predicate(ply))
+ ply.Connection.Send(segment, Channel);
+ }
+ }
+
+ break;
+ }
+ }
+
+ private float[] WavToPcm(string path)
+ {
+ using FileStream fs = File.OpenRead(path);
+ using BinaryReader br = new(fs);
+
+ SkipWavHeader(br);
+
+ int samples = (int)((fs.Length - fs.Position) / 2);
+ float[] pcm = new float[samples];
+
+ for (int i = 0; i < samples; i++)
+ pcm[i] = br.ReadInt16() / 32768f;
+
+ return pcm;
+ }
+
+ private void OnToyRemoved(AdminToyBase toy)
+ {
+ if (toy != Base)
+ return;
+
+ Dispose();
+ }
+
+ private void Dispose()
+ {
+ AdminToyBase.OnRemoved -= OnToyRemoved;
+
+ Stop();
+ encoder?.Dispose();
+ }
}
}
diff --git a/EXILED/Exiled.API/Interfaces/IPcmSource.cs b/EXILED/Exiled.API/Interfaces/IPcmSource.cs
new file mode 100644
index 0000000000..59cdf6b88d
--- /dev/null
+++ b/EXILED/Exiled.API/Interfaces/IPcmSource.cs
@@ -0,0 +1,36 @@
+// -----------------------------------------------------------------------
+//
+// 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; }
+
+ ///
+ /// 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);
+
+ ///
+ /// Resets the PCM source to its initial state, allowing reading from the beginning.
+ ///
+ void Reset();
+ }
+}
From 959fe93e80df663a38d66d6cf30a7703b92e8e96 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Sat, 3 Jan 2026 14:56:05 +0300
Subject: [PATCH 02/38] .
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 45d736a98e..24e877511f 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -33,8 +33,7 @@ namespace Exiled.API.Features.Toys
using Object = UnityEngine.Object;
///
- /// Represents a speaker toy that can play audio in the game world.
- /// Provides methods for playing, stopping, and controlling audio playback, as well as managing playback settings.
+ /// A wrapper class for .
///
public class Speaker : AdminToy, IWrapper
{
From 0c798d4d671d3a473f564d13845b0f5cb994b9d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Sat, 3 Jan 2026 15:05:55 +0300
Subject: [PATCH 03/38] chore: trigger CI
From f02fd3b66ce604952c86dca3f016a84b67294d23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Sat, 3 Jan 2026 15:12:56 +0300
Subject: [PATCH 04/38] added missing doc
---
.../Features/Audio/PreloadedPcmSource.cs | 20 +++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index 3e181704b9..9274003772 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -35,7 +35,9 @@ public PreloadedPcmSource(float[] pcm)
data = pcm;
}
- ///
+ ///
+ /// Gets a value indicating whether the end of the PCM data buffer has been reached.
+ ///
public bool Ended
{
get
@@ -44,7 +46,13 @@ public bool Ended
}
}
- ///
+ ///
+ /// 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);
@@ -54,13 +62,17 @@ public int Read(float[] buffer, int offset, int count)
return read;
}
- ///
+ ///
+ /// Resets the read position to the beginning of the PCM data buffer.
+ ///
public void Reset()
{
pos = 0;
}
- ///
+ ///
+ /// Releases all resources used by the .
+ ///
public void Dispose()
{
}
From b93a35074c0ed6e32397bfed5f90ca7a4bcdce55 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sat, 3 Jan 2026 15:44:22 +0300
Subject: [PATCH 05/38] Change default stream parameter to false in PlayWav
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 24e877511f..7b718d3b35 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -254,7 +254,7 @@ public static void Play(AudioMessage message, IEnumerable targets = null
/// Whether to stream the audio or preload it.
/// Whether to destroy the speaker after playback.
/// Whether to loop the audio.
- public void PlayWav(string path, bool stream = true, bool destroyAfter = false, bool loop = false)
+ public void PlayWav(string path, bool stream = false, bool destroyAfter = false, bool loop = false)
{
Stop();
From 63fc8bf575b6d3c2e38a0c1b95dc739ffc10c1ef Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sat, 3 Jan 2026 15:51:12 +0300
Subject: [PATCH 06/38] Improve XML documentation for Channel property
Updated XML documentation for the Channel property to include a reference to Channels.
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 7b718d3b35..40f9bd9ebf 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -72,7 +72,7 @@ internal Speaker(SpeakerToy speakerToy)
public SpeakerToy Base { get; }
///
- /// Gets or sets the network channel used for sending audio packets from this speaker.
+ /// Gets or sets the network channel used for sending audio packets from this speaker .
///
public int Channel { get; set; }
From a2d006e5cd58176a63754ec6616771a1000b075f Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sat, 3 Jan 2026 21:09:18 +0300
Subject: [PATCH 07/38] 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
---
.../Features/Audio/PreloadedPcmSource.cs | 8 +-
.../Features/Audio/WavStreamSource.cs | 5 +-
.../Exiled.API/Features/Audio/WavUtility.cs | 81 +++++++++++++++++++
EXILED/Exiled.API/Features/Toys/Speaker.cs | 61 +-------------
4 files changed, 90 insertions(+), 65 deletions(-)
create mode 100644 EXILED/Exiled.API/Features/Audio/WavUtility.cs
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index 9274003772..1513b0d0ce 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -14,7 +14,7 @@ namespace Exiled.API.Features.Audio
///
/// Represents a preloaded PCM audio source.
///
- public class PreloadedPcmSource : IPcmSource
+ public sealed class PreloadedPcmSource : IPcmSource
{
///
/// The PCM data buffer.
@@ -29,10 +29,10 @@ public class PreloadedPcmSource : IPcmSource
///
/// Initializes a new instance of the class.
///
- /// The PCM data to preload.
- public PreloadedPcmSource(float[] pcm)
+ /// The file path to the WAV audio file to preload as PCM data.
+ public PreloadedPcmSource(string path)
{
- data = pcm;
+ data = WavUtility.WavToPcm(path);
}
///
diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
index 01f3944297..bbf85cd08b 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -9,13 +9,12 @@ namespace Exiled.API.Features.Audio
{
using System.IO;
- using Exiled.API.Features.Toys;
using Exiled.API.Interfaces;
///
/// Provides a PCM audio source from a WAV file stream.
///
- public class WavStreamSource : IPcmSource
+ public sealed class WavStreamSource : IPcmSource
{
private readonly long endPosition;
private readonly long startPosition;
@@ -28,7 +27,7 @@ public class WavStreamSource : IPcmSource
public WavStreamSource(string path)
{
reader = new BinaryReader(File.OpenRead(path));
- Speaker.SkipWavHeader(reader);
+ WavUtility.SkipHeader(reader);
startPosition = reader.BaseStream.Position;
endPosition = reader.BaseStream.Length;
}
diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
new file mode 100644
index 0000000000..5603db3632
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -0,0 +1,81 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Audio
+{
+ using System.IO;
+
+ using VoiceChat;
+
+ ///
+ /// Provides utility methods for working with WAV audio files, such as converting to PCM data and validating headers.
+ ///
+ public static class WavUtility
+ {
+ ///
+ /// 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 = File.OpenRead(path);
+ using BinaryReader br = new(fs);
+
+ SkipHeader(br);
+
+ int samples = (int)((fs.Length - fs.Position) / 2);
+ float[] pcm = new float[samples];
+
+ for (int i = 0; i < samples; i++)
+ pcm[i] = br.ReadInt16() / 32768f;
+
+ return pcm;
+ }
+
+ ///
+ /// Skips the WAV file header and validates that the format is PCM16 mono with the specified sample rate.
+ ///
+ /// The to read from.
+ ///
+ /// Thrown if the WAV file is not PCM16, mono, or does not match the expected sample rate.
+ ///
+ public static void SkipHeader(BinaryReader br)
+ {
+ br.ReadBytes(12);
+
+ while (true)
+ {
+ string chunk = new(br.ReadChars(4));
+ int size = br.ReadInt32();
+
+ if (chunk == "fmt ")
+ {
+ short format = br.ReadInt16();
+ short channels = br.ReadInt16();
+ int rate = br.ReadInt32();
+ br.ReadInt32();
+ br.ReadInt16();
+ short bits = br.ReadInt16();
+
+ if (format != 1 || channels != 1 || rate != VoiceChatSettings.SampleRate || bits != 16)
+ Log.Error($"Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz.");
+
+ br.BaseStream.Position += size - 16;
+ }
+ else if (chunk == "data")
+ {
+ return;
+ }
+ else
+ {
+ br.BaseStream.Position += size;
+ }
+ }
+ }
+ }
+}
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 40f9bd9ebf..aa99c74d25 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -37,9 +37,8 @@ namespace Exiled.API.Features.Toys
///
public class Speaker : AdminToy, IWrapper
{
- private const int SampleRate = VoiceChatSettings.SampleRate;
private const int FrameSize = VoiceChatSettings.PacketSizePerChannel;
- private const float FrameTime = (float)FrameSize / SampleRate;
+ private const float FrameTime = (float)FrameSize / VoiceChatSettings.SampleRate;
private readonly OpusEncoder encoder;
private readonly float[] frame = new float[FrameSize];
@@ -94,7 +93,7 @@ internal Speaker(SpeakerToy speakerToy)
///
/// Gets or sets the list of target players who will hear the audio played by this speaker when is set to .
///
- public List TargetPlayers { get; set; }
+ public HashSet TargetPlayers { get; set; }
///
/// Gets or sets the predicate used to determine which players will hear the audio when is set to .
@@ -260,7 +259,7 @@ public void PlayWav(string path, bool stream = false, bool destroyAfter = false,
Loop = loop;
DestroyAfter = destroyAfter;
- source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(WavToPcm(path));
+ source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(path);
playBackRoutine = Timing.RunCoroutine(PlayBackCoroutine().CancelWith(GameObject));
}
@@ -276,44 +275,6 @@ public void Stop()
source = null;
}
- ///
- /// Skips the WAV header.
- ///
- /// The binary reader.
- internal static void SkipWavHeader(BinaryReader br)
- {
- br.ReadBytes(12);
-
- while (true)
- {
- string chunk = new(br.ReadChars(4));
- int size = br.ReadInt32();
-
- if (chunk == "fmt ")
- {
- short format = br.ReadInt16();
- short channels = br.ReadInt16();
- int rate = br.ReadInt32();
- br.ReadInt32();
- br.ReadInt16();
- short bits = br.ReadInt16();
-
- if (format != 1 || channels != 1 || rate != SampleRate || bits != 16)
- Log.Error("WAV must be PCM16 mono 48kHz");
-
- br.BaseStream.Position += size - 16;
- }
- else if (chunk == "data")
- {
- return;
- }
- else
- {
- br.BaseStream.Position += size;
- }
- }
- }
-
private IEnumerator PlayBackCoroutine()
{
timeAccumulator = 0f;
@@ -399,22 +360,6 @@ private void SendPacket(int len)
}
}
- private float[] WavToPcm(string path)
- {
- using FileStream fs = File.OpenRead(path);
- using BinaryReader br = new(fs);
-
- SkipWavHeader(br);
-
- int samples = (int)((fs.Length - fs.Position) / 2);
- float[] pcm = new float[samples];
-
- for (int i = 0; i < samples; i++)
- pcm[i] = br.ReadInt16() / 32768f;
-
- return pcm;
- }
-
private void OnToyRemoved(AdminToyBase toy)
{
if (toy != Base)
From 67b3348d323ea1d5c694e1b06fa861b54f245070 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sat, 3 Jan 2026 23:56:59 +0300
Subject: [PATCH 08/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index aa99c74d25..a03265c7c3 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -344,10 +344,10 @@ private void SendPacket(int len)
break;
case SpeakerPlayMode.Predicate:
- using (NetworkWriterPooled writer2 = NetworkWriterPool.Get())
+ using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
- NetworkMessages.Pack(msg, writer2);
- ArraySegment segment = writer2.ToArraySegment();
+ NetworkMessages.Pack(msg, writer);
+ ArraySegment segment = writer.ToArraySegment();
foreach (Player ply in Player.List)
{
From 21c91cce3559a8d1946263ed5c554443125d896d Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sun, 4 Jan 2026 00:37:31 +0300
Subject: [PATCH 09/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index a03265c7c3..50046ea06e 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -255,6 +255,12 @@ public static void Play(AudioMessage message, IEnumerable targets = null
/// Whether to loop the audio.
public void PlayWav(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.");
+
Stop();
Loop = loop;
From 052e46dd5eb316a1536c4b70909989020d92378f Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sun, 4 Jan 2026 02:38:21 +0300
Subject: [PATCH 10/38] Update performance
---
.../Features/Audio/WavStreamSource.cs | 30 ++++++++++++++-----
.../Exiled.API/Features/Audio/WavUtility.cs | 20 +++++++++----
EXILED/Exiled.API/Features/Toys/Speaker.cs | 3 +-
3 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
index bbf85cd08b..0f7058ab3b 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -7,10 +7,14 @@
namespace Exiled.API.Features.Audio
{
+ using System;
using System.IO;
+ using System.Runtime.InteropServices;
using Exiled.API.Interfaces;
+ using VoiceChat;
+
///
/// Provides a PCM audio source from a WAV file stream.
///
@@ -19,6 +23,7 @@ public sealed class WavStreamSource : IPcmSource
private readonly long endPosition;
private readonly long startPosition;
private readonly BinaryReader reader;
+ private readonly byte[] readBuffer = new byte[VoiceChatSettings.PacketSizePerChannel * 2];
///
/// Initializes a new instance of the class.
@@ -52,15 +57,26 @@ public bool Ended
/// The number of samples read.
public int Read(float[] buffer, int offset, int count)
{
- int i = 0;
+ int bytesNeeded = count * 2;
- while (i < count && reader.BaseStream.Position < endPosition)
- {
- buffer[offset + i] = reader.ReadInt16() / 32768f;
- i++;
- }
+ if (bytesNeeded > readBuffer.Length)
+ bytesNeeded = readBuffer.Length;
+
+ int bytesRead = reader.Read(readBuffer, 0, bytesNeeded);
+ if (bytesRead == 0)
+ return 0;
+
+ if (bytesRead % 2 != 0)
+ bytesRead--;
+
+ Span byteSpan = readBuffer.AsSpan(0, bytesRead);
+ Span shortSpan = MemoryMarshal.Cast(byteSpan);
+
+ int samplesRead = shortSpan.Length;
+ for (int i = 0; i < samplesRead; i++)
+ buffer[offset + i] = shortSpan[i] / 32768f;
- return i;
+ return samplesRead;
}
///
diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
index 5603db3632..8b00e8a8f6 100644
--- a/EXILED/Exiled.API/Features/Audio/WavUtility.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -7,7 +7,9 @@
namespace Exiled.API.Features.Audio
{
+ using System;
using System.IO;
+ using System.Runtime.InteropServices;
using VoiceChat;
@@ -23,16 +25,22 @@ public static class WavUtility
/// An array of floats representing the PCM data.
public static float[] WavToPcm(string path)
{
- using FileStream fs = File.OpenRead(path);
- using BinaryReader br = new(fs);
+ byte[] fileBytes = File.ReadAllBytes(path);
+
+ using MemoryStream ms = new(fileBytes);
+ using BinaryReader br = new(ms);
SkipHeader(br);
- int samples = (int)((fs.Length - fs.Position) / 2);
- float[] pcm = new float[samples];
+ int headerOffset = (int)ms.Position;
+ int dataLength = fileBytes.Length - headerOffset;
+
+ Span audioDataSpan = fileBytes.AsSpan(headerOffset, dataLength);
+ Span samples = MemoryMarshal.Cast(audioDataSpan);
- for (int i = 0; i < samples; i++)
- pcm[i] = br.ReadInt16() / 32768f;
+ float[] pcm = new float[samples.Length];
+ for (int i = 0; i < samples.Length; i++)
+ pcm[i] = samples[i] / 32768f;
return pcm;
}
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 50046ea06e..a64c9b2e42 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -309,8 +309,7 @@ private IEnumerator PlayBackCoroutine()
if (Loop)
{
source.Reset();
- timeAccumulator = 0f;
- break;
+ continue;
}
if (DestroyAfter)
From 51298eef23e173786b1c53f7d1dcc9abb274162e Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Sun, 4 Jan 2026 12:38:55 +0300
Subject: [PATCH 11/38] Update Speaker.cs
Update PreloadedPcmSource.cs
Added another constructor for public usages
Update PreloadedPcmSource.cs
---
EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs | 6 +++---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index 1513b0d0ce..e078b8c6ce 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -29,10 +29,10 @@ public sealed class PreloadedPcmSource : IPcmSource
///
/// Initializes a new instance of the class.
///
- /// The file path to the WAV audio file to preload as PCM data.
- public PreloadedPcmSource(string path)
+ /// The raw PCM float array.
+ public PreloadedPcmSource(float[] pcmData)
{
- data = WavUtility.WavToPcm(path);
+ data = pcmData;
}
///
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index a64c9b2e42..ee76feabea 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -265,7 +265,7 @@ public void PlayWav(string path, bool stream = false, bool destroyAfter = false,
Loop = loop;
DestroyAfter = destroyAfter;
- source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(path);
+ source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(WavUtility.WavToPcm(path));
playBackRoutine = Timing.RunCoroutine(PlayBackCoroutine().CancelWith(GameObject));
}
From 3f7ce95399514ee8452251b4e5bde83669b9ece2 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Mon, 5 Jan 2026 13:20:09 +0300
Subject: [PATCH 12/38] 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.
---
.../Features/Audio/PreloadedPcmSource.cs | 46 ++++++++++++-
.../Features/Audio/WavStreamSource.cs | 35 ++++++++++
EXILED/Exiled.API/Features/Toys/Speaker.cs | 68 ++++++++++++++++++-
EXILED/Exiled.API/Interfaces/IPcmSource.cs | 16 +++++
4 files changed, 159 insertions(+), 6 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index e078b8c6ce..abfc4ab8c2 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -11,6 +11,8 @@ namespace Exiled.API.Features.Audio
using Exiled.API.Interfaces;
+ using VoiceChat;
+
///
/// Represents a preloaded PCM audio source.
///
@@ -26,6 +28,15 @@ public sealed class PreloadedPcmSource : IPcmSource
///
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.
///
@@ -46,6 +57,20 @@ public bool Ended
}
}
+ ///
+ /// 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.
///
@@ -62,6 +87,23 @@ public int Read(float[] buffer, int offset, int count)
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.
///
@@ -70,9 +112,7 @@ public void Reset()
pos = 0;
}
- ///
- /// Releases all resources used by the .
- ///
+ ///
public void Dispose()
{
}
diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
index 0f7058ab3b..4ed8e566af 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -37,6 +37,20 @@ public WavStreamSource(string path)
endPosition = reader.BaseStream.Length;
}
+ ///
+ /// 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 => (reader.BaseStream.Position - startPosition) / 2.0 / VoiceChatSettings.SampleRate;
+ set => Seek(value);
+ }
+
///
/// Gets a value indicating whether the end of the stream has been reached.
///
@@ -79,6 +93,27 @@ public int Read(float[] buffer, int offset, int count)
return samplesRead;
}
+ ///
+ /// 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--;
+
+ reader.BaseStream.Position = newPos;
+ }
+
///
/// Resets the stream position to the start.
///
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index ee76feabea..0fb781e524 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -60,6 +60,32 @@ internal Speaker(SpeakerToy speakerToy)
AdminToyBase.OnRemoved += OnToyRemoved;
}
+ ///
+ /// 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 starts.
+ ///
+ public event Action OnPlaybackStarted;
+
+ ///
+ /// Invoked when the audio playback stops completely (either manually or end of file).
+ ///
+ public event Action OnPlaybackStopped;
+
+ ///
+ /// Invoked when the audio track finishes playing.
+ /// If looping is enabled, this triggers every time the track restarts.
+ ///
+ public event Action OnPlaybackFinished;
+
///
/// Gets the prefab.
///
@@ -118,7 +144,17 @@ public bool IsPlaying
public bool IsPaused
{
get => playBackRoutine.IsAliveAndPaused;
- set => playBackRoutine.IsAliveAndPaused = value;
+ set
+ {
+ if (!playBackRoutine.IsRunning)
+ return;
+
+ playBackRoutine.IsAliveAndPaused = value;
+ if (value)
+ OnPlaybackPaused?.Invoke();
+ else
+ OnPlaybackResumed?.Invoke();
+ }
}
///
@@ -182,6 +218,26 @@ public byte ControllerId
set => Base.NetworkControllerId = value;
}
+ ///
+ /// 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;
+ }
+ }
+
+ ///
+ /// Gets the total duration of the current track in seconds.
+ /// Returns 0 if not playing.
+ ///
+ public double Duration => source?.TotalDuration ?? 0.0;
+
///
/// Creates a new .
///
@@ -275,7 +331,10 @@ public void PlayWav(string path, bool stream = false, bool destroyAfter = false,
public void Stop()
{
if (playBackRoutine.IsRunning)
+ {
Timing.KillCoroutines(playBackRoutine);
+ OnPlaybackStopped?.Invoke();
+ }
source?.Dispose();
source = null;
@@ -283,6 +342,7 @@ public void Stop()
private IEnumerator PlayBackCoroutine()
{
+ OnPlaybackStarted?.Invoke();
timeAccumulator = 0f;
while (true)
@@ -306,6 +366,8 @@ private IEnumerator PlayBackCoroutine()
if (!source.Ended)
continue;
+ OnPlaybackFinished?.Invoke();
+
if (Loop)
{
source.Reset();
@@ -313,9 +375,9 @@ private IEnumerator PlayBackCoroutine()
}
if (DestroyAfter)
- {
NetworkServer.Destroy(GameObject);
- }
+ else
+ Stop();
yield break;
}
diff --git a/EXILED/Exiled.API/Interfaces/IPcmSource.cs b/EXILED/Exiled.API/Interfaces/IPcmSource.cs
index 59cdf6b88d..680f568410 100644
--- a/EXILED/Exiled.API/Interfaces/IPcmSource.cs
+++ b/EXILED/Exiled.API/Interfaces/IPcmSource.cs
@@ -19,6 +19,16 @@ public interface IPcmSource : IDisposable
///
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.
///
@@ -28,6 +38,12 @@ public interface IPcmSource : IDisposable
/// 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.
///
From 9285154cc96c707accca5890150dc68deca1c02f Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Mon, 5 Jan 2026 13:34:07 +0300
Subject: [PATCH 13/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 0fb781e524..eeb541354d 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -321,7 +321,7 @@ public void PlayWav(string path, bool stream = false, bool destroyAfter = false,
Loop = loop;
DestroyAfter = destroyAfter;
- source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(WavUtility.WavToPcm(path));
+ source = stream ? new WavStreamSource(path) : new PreloadedPcmSource(path);
playBackRoutine = Timing.RunCoroutine(PlayBackCoroutine().CancelWith(GameObject));
}
From 072aba892e3caf2e2150fb64300e8f690524f3cf Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Tue, 6 Jan 2026 13:34:25 +0300
Subject: [PATCH 14/38] Update WavUtility.cs
---
EXILED/Exiled.API/Features/Audio/WavUtility.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
index 8b00e8a8f6..d1085450bd 100644
--- a/EXILED/Exiled.API/Features/Audio/WavUtility.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -14,7 +14,7 @@ namespace Exiled.API.Features.Audio
using VoiceChat;
///
- /// Provides utility methods for working with WAV audio files, such as converting to PCM data and validating headers.
+ /// Provides utility methods for working with WAV audio files.
///
public static class WavUtility
{
@@ -49,9 +49,6 @@ public static float[] WavToPcm(string path)
/// Skips the WAV file header and validates that the format is PCM16 mono with the specified sample rate.
///
/// The to read from.
- ///
- /// Thrown if the WAV file is not PCM16, mono, or does not match the expected sample rate.
- ///
public static void SkipHeader(BinaryReader br)
{
br.ReadBytes(12);
From 6b97e0f5ab35f94c2170a3c6288d99c70becb232 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Tue, 6 Jan 2026 13:52:17 +0300
Subject: [PATCH 15/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index eeb541354d..406a41e086 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -40,7 +40,7 @@ public class Speaker : AdminToy, IWrapper
private const int FrameSize = VoiceChatSettings.PacketSizePerChannel;
private const float FrameTime = (float)FrameSize / VoiceChatSettings.SampleRate;
- private readonly OpusEncoder encoder;
+ private readonly OpusEncoder encoder = new(OpusApplicationType.Audio);
private readonly float[] frame = new float[FrameSize];
private readonly byte[] encoded = new byte[VoiceChatSettings.MaxEncodedSize];
@@ -56,7 +56,6 @@ internal Speaker(SpeakerToy speakerToy)
: base(speakerToy, AdminToyType.Speaker)
{
Base = speakerToy;
- encoder = new OpusEncoder(OpusApplicationType.Audio);
AdminToyBase.OnRemoved += OnToyRemoved;
}
From 49df481159b1e07706f1cee568b0290a8d12e8dc Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Tue, 6 Jan 2026 16:58:54 +0300
Subject: [PATCH 16/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 38 +++++++++++++++-------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 406a41e086..c79bf05ef7 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -40,13 +40,13 @@ public class Speaker : AdminToy, IWrapper
private const int FrameSize = VoiceChatSettings.PacketSizePerChannel;
private const float FrameTime = (float)FrameSize / VoiceChatSettings.SampleRate;
- private readonly OpusEncoder encoder = new(OpusApplicationType.Audio);
- private readonly float[] frame = new float[FrameSize];
- private readonly byte[] encoded = new byte[VoiceChatSettings.MaxEncodedSize];
-
+ private float[] frame;
+ private byte[] encoded;
private IPcmSource source;
+ private OpusEncoder encoder;
private float timeAccumulator;
private CoroutineHandle playBackRoutine;
+ private bool isPlayBackInitialized = false;
///
/// Initializes a new instance of the class.
@@ -56,7 +56,6 @@ internal Speaker(SpeakerToy speakerToy)
: base(speakerToy, AdminToyType.Speaker)
{
Base = speakerToy;
- AdminToyBase.OnRemoved += OnToyRemoved;
}
///
@@ -314,8 +313,9 @@ public void PlayWav(string path, bool stream = false, bool destroyAfter = false,
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.");
+ throw new NotSupportedException($"The file type '{Path.GetExtension(path)}' is not supported. Please use .wav file.");
+ InitializePlayBack();
Stop();
Loop = loop;
@@ -339,6 +339,20 @@ public void Stop()
source = null;
}
+ private void InitializePlayBack()
+ {
+ if (isPlayBackInitialized)
+ return;
+
+ isPlayBackInitialized = true;
+
+ frame = new float[FrameSize];
+ encoder = new(OpusApplicationType.Audio);
+ encoded = new byte[VoiceChatSettings.MaxEncodedSize];
+
+ AdminToyBase.OnRemoved += OnToyRemoved;
+ }
+
private IEnumerator PlayBackCoroutine()
{
OnPlaybackStarted?.Invoke();
@@ -431,15 +445,17 @@ private void OnToyRemoved(AdminToyBase toy)
if (toy != Base)
return;
- Dispose();
- }
-
- private void Dispose()
- {
AdminToyBase.OnRemoved -= OnToyRemoved;
+ if (!isPlayBackInitialized)
+ return;
+
Stop();
encoder?.Dispose();
+ encoder = null;
+ frame = null;
+ encoded = null;
+ isPlayBackInitialized = false;
}
}
}
From ccca1abadbb26257c8bee0bda5448e252a286573 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Tue, 6 Jan 2026 17:14:10 +0300
Subject: [PATCH 17/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index c79bf05ef7..3195bdce3d 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -147,6 +147,9 @@ public bool IsPaused
if (!playBackRoutine.IsRunning)
return;
+ if (playBackRoutine.IsAliveAndPaused == value)
+ return;
+
playBackRoutine.IsAliveAndPaused = value;
if (value)
OnPlaybackPaused?.Invoke();
@@ -447,14 +450,12 @@ private void OnToyRemoved(AdminToyBase toy)
AdminToyBase.OnRemoved -= OnToyRemoved;
- if (!isPlayBackInitialized)
- return;
-
Stop();
encoder?.Dispose();
encoder = null;
frame = null;
encoded = null;
+
isPlayBackInitialized = false;
}
}
From 85193d984e1c143754390b93ad6df8470805c118 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Wed, 7 Jan 2026 00:01:59 +0300
Subject: [PATCH 18/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 3195bdce3d..2948694b1a 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -237,7 +237,10 @@ public double CurrentTime
/// Gets the total duration of the current track in seconds.
/// Returns 0 if not playing.
///
- public double Duration => source?.TotalDuration ?? 0.0;
+ public double Duration
+ {
+ get => source?.TotalDuration ?? 0.0;
+ }
///
/// Creates a new .
From 89fe70ddcb8302eb9e2d67349c3a803a70a05e8a Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Wed, 7 Jan 2026 15:27:52 +0300
Subject: [PATCH 19/38] Added OnPlaybackLooped event
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 2948694b1a..5e036d91b0 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -58,6 +58,11 @@ internal Speaker(SpeakerToy speakerToy)
Base = speakerToy;
}
+ ///
+ /// Invoked when the audio playback starts.
+ ///
+ public event Action OnPlaybackStarted;
+
///
/// Invoked when the audio playback is paused.
///
@@ -69,20 +74,20 @@ internal Speaker(SpeakerToy speakerToy)
public event Action OnPlaybackResumed;
///
- /// Invoked when the audio playback starts.
+ /// Invoked when the audio playback loops back to the beginning.
///
- public event Action OnPlaybackStarted;
+ public event Action OnPlaybackLooped;
///
- /// Invoked when the audio playback stops completely (either manually or end of file).
+ /// Invoked when the audio track finishes playing.
+ /// If looping is enabled, this triggers every time the track finished.
///
- public event Action OnPlaybackStopped;
+ public event Action OnPlaybackFinished;
///
- /// Invoked when the audio track finishes playing.
- /// If looping is enabled, this triggers every time the track restarts.
+ /// Invoked when the audio playback stops completely (either manually or end of file).
///
- public event Action OnPlaybackFinished;
+ public event Action OnPlaybackStopped;
///
/// Gets the prefab.
@@ -390,6 +395,7 @@ private IEnumerator PlayBackCoroutine()
if (Loop)
{
source.Reset();
+ OnPlaybackLooped?.Invoke();
continue;
}
From ae9992f5a2b891d110ad04dc5a1bbd2b67042234 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Mon, 12 Jan 2026 01:41:25 +0300
Subject: [PATCH 20/38] Update Speaker.cs
Refactor: Optimize WavStreamSource with ArrayPool & dynamic buffering
Feat: Add Pitch (it can be reversed if you wish)
Update EXILED.props
---
EXILED/EXILED.props | 2 +-
.../Features/Audio/WavStreamSource.cs | 41 ++++--
EXILED/Exiled.API/Features/Toys/Speaker.cs | 128 ++++++++++++++++--
3 files changed, 147 insertions(+), 24 deletions(-)
diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props
index edc17d4fb2..b3cea192e3 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/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
index 4ed8e566af..2ff35f6623 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -8,6 +8,7 @@
namespace Exiled.API.Features.Audio
{
using System;
+ using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
@@ -23,7 +24,6 @@ public sealed class WavStreamSource : IPcmSource
private readonly long endPosition;
private readonly long startPosition;
private readonly BinaryReader reader;
- private readonly byte[] readBuffer = new byte[VoiceChatSettings.PacketSizePerChannel * 2];
///
/// Initializes a new instance of the class.
@@ -73,24 +73,36 @@ public int Read(float[] buffer, int offset, int count)
{
int bytesNeeded = count * 2;
- if (bytesNeeded > readBuffer.Length)
- bytesNeeded = readBuffer.Length;
+ byte[] tempBuffer = ArrayPool.Shared.Rent(bytesNeeded);
- int bytesRead = reader.Read(readBuffer, 0, bytesNeeded);
- if (bytesRead == 0)
- return 0;
+ try
+ {
+ int bytesRead = reader.Read(tempBuffer, 0, bytesNeeded);
+
+ if (bytesRead == 0)
+ return 0;
- if (bytesRead % 2 != 0)
- bytesRead--;
+ if (bytesRead % 2 != 0)
+ bytesRead--;
- Span byteSpan = readBuffer.AsSpan(0, bytesRead);
- Span shortSpan = MemoryMarshal.Cast(byteSpan);
+ Span byteSpan = tempBuffer.AsSpan(0, bytesRead);
+ Span shortSpan = MemoryMarshal.Cast(byteSpan);
- int samplesRead = shortSpan.Length;
- for (int i = 0; i < samplesRead; i++)
- buffer[offset + i] = shortSpan[i] / 32768f;
+ int samplesRead = shortSpan.Length;
+ for (int i = 0; i < samplesRead; i++)
+ {
+ if (offset + i >= buffer.Length)
+ break;
- return samplesRead;
+ buffer[offset + i] = shortSpan[i] / 32768f;
+ }
+
+ return samplesRead;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(tempBuffer);
+ }
}
///
@@ -105,6 +117,7 @@ public void Seek(double seconds)
long newPos = startPosition + targetByte;
if (newPos > endPosition)
newPos = endPosition;
+
if (newPos < startPosition)
newPos = startPosition;
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 5e036d91b0..91f1c62a72 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -42,10 +42,16 @@ public class Speaker : AdminToy, IWrapper
private float[] frame;
private byte[] encoded;
+ private float[] resampleBuffer;
+
+ private double resampleTime;
+ private int resampleBufferFilled;
+
private IPcmSource source;
private OpusEncoder encoder;
- private float timeAccumulator;
private CoroutineHandle playBackRoutine;
+
+ private bool isPitchDefault = true;
private bool isPlayBackInitialized = false;
///
@@ -104,6 +110,24 @@ internal Speaker(SpeakerToy speakerToy)
///
public int Channel { get; set; }
+ ///
+ /// Gets or sets the playback pitch.
+ ///
+ public float Pitch
+ {
+ get;
+ set
+ {
+ field = value;
+ isPitchDefault = Math.Abs(field - 1.0f) < 0.0001f;
+ if (isPitchDefault)
+ {
+ resampleTime = 0.0;
+ resampleBufferFilled = 0;
+ }
+ }
+ }
+
///
/// Gets or sets a value indicating whether the audio playback should loop when it reaches the end.
///
@@ -234,7 +258,11 @@ public double CurrentTime
set
{
if (source != null)
+ {
source.CurrentTime = value;
+ resampleTime = 0.0;
+ resampleBufferFilled = 0;
+ }
}
}
@@ -318,7 +346,7 @@ public static void Play(AudioMessage message, IEnumerable targets = null
/// Whether to stream the audio or preload it.
/// Whether to destroy the speaker after playback.
/// Whether to loop the audio.
- public void PlayWav(string path, bool stream = false, bool destroyAfter = false, bool loop = false)
+ 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);
@@ -358,6 +386,7 @@ private void InitializePlayBack()
isPlayBackInitialized = true;
frame = new float[FrameSize];
+ resampleBuffer = Array.Empty();
encoder = new(OpusApplicationType.Audio);
encoded = new byte[VoiceChatSettings.MaxEncodedSize];
@@ -367,7 +396,11 @@ private void InitializePlayBack()
private IEnumerator PlayBackCoroutine()
{
OnPlaybackStarted?.Invoke();
- timeAccumulator = 0f;
+
+ resampleTime = 0.0;
+ resampleBufferFilled = 0;
+
+ float timeAccumulator = 0f;
while (true)
{
@@ -377,10 +410,16 @@ private IEnumerator PlayBackCoroutine()
{
timeAccumulator -= FrameTime;
- int read = source.Read(frame, 0, FrameSize);
-
- if (read < FrameSize)
- Array.Clear(frame, read, FrameSize - read);
+ 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);
@@ -396,6 +435,7 @@ private IEnumerator PlayBackCoroutine()
{
source.Reset();
OnPlaybackLooped?.Invoke();
+ resampleTime = resampleBufferFilled = 0;
continue;
}
@@ -411,11 +451,81 @@ private IEnumerator PlayBackCoroutine()
}
}
+ 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)
+ switch (PlayMode)
{
case SpeakerPlayMode.Global:
NetworkServer.SendToReady(msg, Channel);
@@ -464,7 +574,7 @@ private void OnToyRemoved(AdminToyBase toy)
encoder = null;
frame = null;
encoded = null;
-
+ resampleBuffer = null;
isPlayBackInitialized = false;
}
}
From 889ca197ee09dac4b0ad59d19283209f06288036 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Mon, 12 Jan 2026 12:13:24 +0300
Subject: [PATCH 21/38] Update to c# lang version 14
---
EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs | 6 +++---
.../Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
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;
From 895fba89a24344a12b08c1c6aa18eaa78834065c Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:34:01 +0300
Subject: [PATCH 22/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 91f1c62a72..a477ff87b6 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -59,10 +59,7 @@ public class Speaker : AdminToy, IWrapper
///
/// The of the toy.
internal Speaker(SpeakerToy speakerToy)
- : base(speakerToy, AdminToyType.Speaker)
- {
- Base = speakerToy;
- }
+ : base(speakerToy, AdminToyType.Speaker) => Base = speakerToy;
///
/// Invoked when the audio playback starts.
From 3abd0b183233566fdce2dfd05db8e05a9d9cd91c Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Mon, 12 Jan 2026 14:05:42 +0300
Subject: [PATCH 23/38] Enum 4 byte to 1 byte
---
EXILED/Exiled.API/Enums/SpeakerPlayMode.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
index 8ab0a46117..f94c3bc360 100644
--- a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
+++ b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
@@ -10,21 +10,21 @@ namespace Exiled.API.Enums
///
/// Specifies the available modes for playing audio through a speaker.
///
- public enum SpeakerPlayMode
+ public enum SpeakerPlayMode : byte
{
///
/// Play audio globally to all players.
///
- Global,
+ Global = 0,
///
/// Play audio to a specific list of players.
///
- PlayerList,
+ PlayerList = 1,
///
/// Play audio to players matching a predicate.
///
- Predicate,
+ Predicate = 2,
}
}
From 88497dea037d78420b8e2ca995a8f9a1379523c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Mon, 12 Jan 2026 16:55:53 +0300
Subject: [PATCH 24/38] -Replaced 'File.ReadAllBytes' with
'ArrayPool.Shared' -Optimized WAV header skipping (string comparisons
to direct uint32 hex checks)
---
.../Exiled.API/Features/Audio/WavUtility.cs | 49 +++++++++++++------
1 file changed, 34 insertions(+), 15 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
index d1085450bd..20e3865ece 100644
--- a/EXILED/Exiled.API/Features/Audio/WavUtility.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -8,6 +8,7 @@
namespace Exiled.API.Features.Audio
{
using System;
+ using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
@@ -25,24 +26,36 @@ public static class WavUtility
/// An array of floats representing the PCM data.
public static float[] WavToPcm(string path)
{
- byte[] fileBytes = File.ReadAllBytes(path);
+ using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+ int length = (int)fs.Length;
- using MemoryStream ms = new(fileBytes);
- using BinaryReader br = new(ms);
+ byte[] rentedBuffer = ArrayPool.Shared.Rent(length);
- SkipHeader(br);
+ try
+ {
+ int bytesRead = fs.Read(rentedBuffer, 0, length);
+
+ using MemoryStream ms = new(rentedBuffer, 0, bytesRead);
+ using BinaryReader br = new(ms);
- int headerOffset = (int)ms.Position;
- int dataLength = fileBytes.Length - headerOffset;
+ SkipHeader(br);
- Span audioDataSpan = fileBytes.AsSpan(headerOffset, dataLength);
- Span samples = MemoryMarshal.Cast(audioDataSpan);
+ int headerOffset = (int)ms.Position;
+ int dataLength = bytesRead - headerOffset;
- float[] pcm = new float[samples.Length];
- for (int i = 0; i < samples.Length; i++)
- pcm[i] = samples[i] / 32768f;
+ Span audioDataSpan = rentedBuffer.AsSpan(headerOffset, dataLength);
+ Span samples = MemoryMarshal.Cast(audioDataSpan);
- return pcm;
+ float[] pcm = new float[samples.Length];
+ for (int i = 0; i < samples.Length; i++)
+ pcm[i] = samples[i] / 32768f;
+
+ return pcm;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(rentedBuffer);
+ }
}
///
@@ -55,10 +68,11 @@ public static void SkipHeader(BinaryReader br)
while (true)
{
- string chunk = new(br.ReadChars(4));
+ uint chunk = br.ReadUInt32();
int size = br.ReadInt32();
- if (chunk == "fmt ")
+ // 'fmt ' chunk
+ if (chunk == 0x20746D66)
{
short format = br.ReadInt16();
short channels = br.ReadInt16();
@@ -72,7 +86,9 @@ public static void SkipHeader(BinaryReader br)
br.BaseStream.Position += size - 16;
}
- else if (chunk == "data")
+
+ // 'data' chunk
+ else if (chunk == 0x61746164)
{
return;
}
@@ -80,6 +96,9 @@ public static void SkipHeader(BinaryReader br)
{
br.BaseStream.Position += size;
}
+
+ if (br.BaseStream.Position >= br.BaseStream.Length)
+ throw new InvalidDataException("WAV file does not contain a 'data' chunk. File might be corrupted or empty.");
}
}
}
From ae2103fc482dc61b673002458c4a3f93698ac343 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Tue, 13 Jan 2026 01:32:39 +0300
Subject: [PATCH 25/38] Refactor
---
.../Features/Audio/PreloadedPcmSource.cs | 8 +-------
.../Features/Audio/WavStreamSource.cs | 18 +++++++++---------
EXILED/Exiled.API/Features/Toys/Speaker.cs | 10 ++--------
3 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index abfc4ab8c2..537c27dfc1 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -49,13 +49,7 @@ public PreloadedPcmSource(float[] pcmData)
///
/// Gets a value indicating whether the end of the PCM data buffer has been reached.
///
- public bool Ended
- {
- get
- {
- return pos >= data.Length;
- }
- }
+ public bool Ended => pos >= data.Length;
///
/// Gets the total duration of the audio in seconds.
diff --git a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
index 2ff35f6623..60eeb26832 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -54,13 +54,7 @@ public double CurrentTime
///
/// Gets a value indicating whether the end of the stream has been reached.
///
- public bool Ended
- {
- get
- {
- return reader.BaseStream.Position >= endPosition;
- }
- }
+ public bool Ended => reader.BaseStream.Position >= endPosition;
///
/// Reads PCM data from the stream into the specified buffer.
@@ -130,11 +124,17 @@ public void Seek(double seconds)
///
/// Resets the stream position to the start.
///
- public void Reset() => reader.BaseStream.Position = startPosition;
+ public void Reset()
+ {
+ reader.BaseStream.Position = startPosition;
+ }
///
/// Releases all resources used by the .
///
- public void Dispose() => reader.Dispose();
+ public void Dispose()
+ {
+ reader.Dispose();
+ }
}
}
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index a477ff87b6..6b119a1c71 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -154,10 +154,7 @@ public float Pitch
///
/// Gets a value indicating whether gets is a sound playing on this speaker or not.
///
- public bool IsPlaying
- {
- get => playBackRoutine.IsRunning && !IsPaused;
- }
+ public bool IsPlaying => playBackRoutine.IsRunning && !IsPaused;
///
/// Gets or sets a value indicating whether the playback is paused.
@@ -267,10 +264,7 @@ public double CurrentTime
/// Gets the total duration of the current track in seconds.
/// Returns 0 if not playing.
///
- public double Duration
- {
- get => source?.TotalDuration ?? 0.0;
- }
+ public double Duration => source?.TotalDuration ?? 0.0;
///
/// Creates a new .
From 1bc0db91922b147917634945b4735a25e6c79b50 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Thu, 15 Jan 2026 14:45:14 +0300
Subject: [PATCH 26/38] Initialize Channel property with ReliableOrdered2
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 6b119a1c71..73c76aa71c 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -105,7 +105,7 @@ internal Speaker(SpeakerToy speakerToy)
///
/// Gets or sets the network channel used for sending audio packets from this speaker .
///
- public int Channel { get; set; }
+ public int Channel { get; set; } = Channels.ReliableOrdered2;
///
/// Gets or sets the playback pitch.
From d6cf318041b8b236a3ee9dcc41d85caef54d6cf4 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:16:06 +0300
Subject: [PATCH 27/38] Refactor Speaker class properties
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 88 +++++++++++-----------
1 file changed, 46 insertions(+), 42 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 73c76aa71c..260fbf56aa 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -107,24 +107,6 @@ internal Speaker(SpeakerToy speakerToy)
///
public int Channel { get; set; } = Channels.ReliableOrdered2;
- ///
- /// Gets or sets the playback pitch.
- ///
- public float Pitch
- {
- get;
- set
- {
- field = value;
- isPitchDefault = Math.Abs(field - 1.0f) < 0.0001f;
- if (isPitchDefault)
- {
- resampleTime = 0.0;
- resampleBufferFilled = 0;
- }
- }
- }
-
///
/// Gets or sets a value indicating whether the audio playback should loop when it reaches the end.
///
@@ -181,6 +163,52 @@ public bool IsPaused
}
}
+ ///
+ /// 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 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 = value;
+ isPitchDefault = Math.Abs(field - 1.0f) < 0.0001f;
+ if (isPitchDefault)
+ {
+ resampleTime = 0.0;
+ resampleBufferFilled = 0;
+ }
+ }
+ }
+
///
/// Gets or sets the volume of the audio source.
///
@@ -242,30 +270,6 @@ public byte ControllerId
set => Base.NetworkControllerId = value;
}
- ///
- /// 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 Duration => source?.TotalDuration ?? 0.0;
-
///
/// Creates a new .
///
From d17ca3612e04adef675f3958e1fb1ec2ae60e884 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:23:28 +0300
Subject: [PATCH 28/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 260fbf56aa..62d0de8751 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -199,7 +199,7 @@ public float Pitch
get;
set
{
- field = value;
+ field = Mathf.Max(0.1f, Mathf.Abs(value));
isPitchDefault = Math.Abs(field - 1.0f) < 0.0001f;
if (isPitchDefault)
{
From 2b1200c1e6cbdbba747f0b3f2c298ff434a9ac88 Mon Sep 17 00:00:00 2001
From: MS-crew <100300664+MS-crew@users.noreply.github.com>
Date: Thu, 15 Jan 2026 19:45:37 +0300
Subject: [PATCH 29/38] Update Speaker.cs
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 62d0de8751..f9d6f42eff 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -200,7 +200,7 @@ public float Pitch
set
{
field = Mathf.Max(0.1f, Mathf.Abs(value));
- isPitchDefault = Math.Abs(field - 1.0f) < 0.0001f;
+ isPitchDefault = Mathf.Abs(field - 1.0f) < 0.0001f;
if (isPitchDefault)
{
resampleTime = 0.0;
From 974b3f18891fe454edc629a6d1b5c561e980d827 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Fri, 16 Jan 2026 16:05:38 +0300
Subject: [PATCH 30/38] =?UTF-8?q?a=C4=9Fhhh?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
EXILED/Exiled.API/Enums/SpeakerPlayMode.cs | 2 +-
.../Features/Audio/PreloadedPcmSource.cs | 2 +-
.../Features/Audio/WavStreamSource.cs | 70 ++++++++++---------
.../Exiled.API/Features/Audio/WavUtility.cs | 51 ++++++++------
EXILED/Exiled.API/Features/Toys/Speaker.cs | 2 +-
5 files changed, 69 insertions(+), 58 deletions(-)
diff --git a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
index f94c3bc360..60bce04f33 100644
--- a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
+++ b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
@@ -27,4 +27,4 @@ public enum SpeakerPlayMode : byte
///
Predicate = 2,
}
-}
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
index 537c27dfc1..7be9d09a30 100644
--- a/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/PreloadedPcmSource.cs
@@ -111,4 +111,4 @@ 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
index 60eeb26832..d177cd28cf 100644
--- a/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavStreamSource.cs
@@ -23,7 +23,9 @@ public sealed class WavStreamSource : IPcmSource
{
private readonly long endPosition;
private readonly long startPosition;
- private readonly BinaryReader reader;
+ private readonly FileStream stream;
+
+ private byte[] internalBuffer;
///
/// Initializes a new instance of the class.
@@ -31,10 +33,11 @@ public sealed class WavStreamSource : IPcmSource
/// The path to the audio file.
public WavStreamSource(string path)
{
- reader = new BinaryReader(File.OpenRead(path));
- WavUtility.SkipHeader(reader);
- startPosition = reader.BaseStream.Position;
- endPosition = reader.BaseStream.Length;
+ stream = File.OpenRead(path);
+ WavUtility.SkipHeader(stream);
+ startPosition = stream.Position;
+ endPosition = stream.Length;
+ internalBuffer = ArrayPool.Shared.Rent(VoiceChatSettings.PacketSizePerChannel * 2);
}
///
@@ -47,14 +50,14 @@ public WavStreamSource(string path)
///
public double CurrentTime
{
- get => (reader.BaseStream.Position - startPosition) / 2.0 / VoiceChatSettings.SampleRate;
+ 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 => reader.BaseStream.Position >= endPosition;
+ public bool Ended => stream.Position >= endPosition;
///
/// Reads PCM data from the stream into the specified buffer.
@@ -67,36 +70,30 @@ public int Read(float[] buffer, int offset, int count)
{
int bytesNeeded = count * 2;
- byte[] tempBuffer = ArrayPool.Shared.Rent(bytesNeeded);
-
- try
+ if (internalBuffer.Length < bytesNeeded)
{
- int bytesRead = reader.Read(tempBuffer, 0, bytesNeeded);
+ ArrayPool.Shared.Return(internalBuffer);
+ internalBuffer = ArrayPool.Shared.Rent(bytesNeeded);
+ }
- if (bytesRead == 0)
- return 0;
+ int bytesRead = stream.Read(internalBuffer, 0, bytesNeeded);
- if (bytesRead % 2 != 0)
- bytesRead--;
+ if (bytesRead == 0)
+ return 0;
- Span byteSpan = tempBuffer.AsSpan(0, bytesRead);
- Span shortSpan = MemoryMarshal.Cast(byteSpan);
+ if (bytesRead % 2 != 0)
+ bytesRead--;
- int samplesRead = shortSpan.Length;
- for (int i = 0; i < samplesRead; i++)
- {
- if (offset + i >= buffer.Length)
- break;
+ Span byteSpan = internalBuffer.AsSpan(0, bytesRead);
+ Span shortSpan = MemoryMarshal.Cast(byteSpan);
- buffer[offset + i] = shortSpan[i] / 32768f;
- }
+ int samplesInDestination = buffer.Length - offset;
+ int samplesToWrite = Math.Min(shortSpan.Length, samplesInDestination);
- return samplesRead;
- }
- finally
- {
- ArrayPool.Shared.Return(tempBuffer);
- }
+ for (int i = 0; i < samplesToWrite; i++)
+ buffer[offset + i] = shortSpan[i] / 32768f;
+
+ return samplesToWrite;
}
///
@@ -118,7 +115,7 @@ public void Seek(double seconds)
if (newPos % 2 != 0)
newPos--;
- reader.BaseStream.Position = newPos;
+ stream.Position = newPos;
}
///
@@ -126,7 +123,7 @@ public void Seek(double seconds)
///
public void Reset()
{
- reader.BaseStream.Position = startPosition;
+ stream.Position = startPosition;
}
///
@@ -134,7 +131,12 @@ public void Reset()
///
public void Dispose()
{
- reader.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
index 20e3865ece..777af5b974 100644
--- a/EXILED/Exiled.API/Features/Audio/WavUtility.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -9,6 +9,7 @@ namespace Exiled.API.Features.Audio
{
using System;
using System.Buffers;
+ using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
@@ -36,9 +37,8 @@ public static float[] WavToPcm(string path)
int bytesRead = fs.Read(rentedBuffer, 0, length);
using MemoryStream ms = new(rentedBuffer, 0, bytesRead);
- using BinaryReader br = new(ms);
- SkipHeader(br);
+ SkipHeader(ms);
int headerOffset = (int)ms.Position;
int dataLength = bytesRead - headerOffset;
@@ -47,6 +47,7 @@ public static float[] WavToPcm(string path)
Span samples = MemoryMarshal.Cast(audioDataSpan);
float[] pcm = new float[samples.Length];
+
for (int i = 0; i < samples.Length; i++)
pcm[i] = samples[i] / 32768f;
@@ -61,45 +62,53 @@ public static float[] WavToPcm(string path)
///
/// 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(BinaryReader br)
+ /// The to read from.
+ public static void SkipHeader(Stream stream)
{
- br.ReadBytes(12);
+ Span headerBuffer = stackalloc byte[12];
+ stream.Read(headerBuffer);
+ Span chunkHeader = stackalloc byte[8];
while (true)
{
- uint chunk = br.ReadUInt32();
- int size = br.ReadInt32();
+ 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 (chunk == 0x20746D66)
+ if (chunkId == 0x20746D66)
{
- short format = br.ReadInt16();
- short channels = br.ReadInt16();
- int rate = br.ReadInt32();
- br.ReadInt32();
- br.ReadInt16();
- short bits = br.ReadInt16();
+ 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)
- Log.Error($"Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz.");
+ throw new InvalidDataException($"Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz.");
- br.BaseStream.Position += size - 16;
+ if (chunkSize > 16)
+ stream.Seek(chunkSize - 16, SeekOrigin.Current);
}
// 'data' chunk
- else if (chunk == 0x61746164)
+ else if (chunkId == 0x61746164)
{
return;
}
else
{
- br.BaseStream.Position += size;
+ stream.Seek(chunkSize, SeekOrigin.Current);
}
- if (br.BaseStream.Position >= br.BaseStream.Length)
- throw new InvalidDataException("WAV file does not contain a 'data' chunk. File might be corrupted or empty.");
+ 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/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index f9d6f42eff..52333f83ef 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -573,4 +573,4 @@ private void OnToyRemoved(AdminToyBase toy)
isPlayBackInitialized = false;
}
}
-}
+}
\ No newline at end of file
From e284fdd679ea09a3e481552aedb3dc52be6eef24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Fri, 16 Jan 2026 21:11:49 +0300
Subject: [PATCH 31/38] renamed method
---
EXILED/Exiled.API/Features/Toys/Speaker.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 52333f83ef..d47b7606c8 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -349,7 +349,7 @@ public void Play(string path, bool stream = false, bool destroyAfter = false, bo
if (!path.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
throw new NotSupportedException($"The file type '{Path.GetExtension(path)}' is not supported. Please use .wav file.");
- InitializePlayBack();
+ TryInitializePlayBack();
Stop();
Loop = loop;
@@ -373,7 +373,7 @@ public void Stop()
source = null;
}
- private void InitializePlayBack()
+ private void TryInitializePlayBack()
{
if (isPlayBackInitialized)
return;
From 83494fa38b0acc1de07435780437bfe06a378f7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?=
Date: Sat, 17 Jan 2026 12:39:50 +0300
Subject: [PATCH 32/38] Added Target Player Play Mode
---
EXILED/Exiled.API/Enums/SpeakerPlayMode.cs | 9 +++++++--
EXILED/Exiled.API/Features/Toys/Speaker.cs | 11 ++++++++++-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
index 60bce04f33..9bafa513e1 100644
--- a/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
+++ b/EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
@@ -17,14 +17,19 @@ public enum SpeakerPlayMode : byte
///
Global = 0,
+ ///
+ /// Play audio to a specific player.
+ ///
+ Player = 1,
+
///
/// Play audio to a specific list of players.
///
- PlayerList = 1,
+ PlayerList = 2,
///
/// Play audio to players matching a predicate.
///
- Predicate = 2,
+ Predicate = 3,
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index d47b7606c8..e91276fd87 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -122,6 +122,11 @@ internal Speaker(SpeakerToy speakerToy)
///
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 .
///
@@ -526,6 +531,10 @@ private void SendPacket(int len)
NetworkServer.SendToReady(msg, Channel);
break;
+ case SpeakerPlayMode.Player:
+ TargetPlayer?.Connection.Send(msg, Channel);
+ break;
+
case SpeakerPlayMode.PlayerList:
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
@@ -534,7 +543,7 @@ private void SendPacket(int len)
foreach (Player ply in TargetPlayers)
{
- ply.Connection.Send(segment, Channel);
+ ply?.Connection.Send(segment, Channel);
}
}
From 4f05e69f86c03597934478563c0042dcb413cf7d Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Sat, 17 Jan 2026 22:00:37 +0100
Subject: [PATCH 33/38] C# 14
BreakingChange for LeftPlayerEvent
---
.../Exiled.API/Extensions/MirrorExtensions.cs | 9 ++----
EXILED/Exiled.API/Features/Camera.cs | 4 +--
EXILED/Exiled.API/Features/Core/EActor.cs | 18 +++++------
.../Features/Core/Generic/EnumClass.cs | 11 ++++---
.../Core/Generic/UnmanagedEnumClass.cs | 11 ++++---
.../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 +-
.../Patches/Generic/Scp559List.cs | 2 +-
40 files changed, 174 insertions(+), 234 deletions(-)
diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs
index 3ba47fd9a8..e88f4dad0c 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..0473bcb764 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 => field;
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 => field;
set
{
if (!IsEditable)
return;
- fixedTickRate = value;
+ field = value;
}
}
diff --git a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
index 0f3077d874..062a12487a 100644
--- a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
+++ b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
@@ -29,8 +29,6 @@ public abstract class EnumClass : IComparable, IEquatable values;
private static bool isDefined;
- private string name;
-
///
/// Initializes a new instance of the class.
///
@@ -61,7 +59,7 @@ public string Name
get
{
if (isDefined)
- return name;
+ return field;
IEnumerable fields = typeof(TObject)
.GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
@@ -70,12 +68,13 @@ public string Name
foreach (FieldInfo @field in fields)
{
TObject instance = (TObject)@field.GetValue(null);
- instance.name = @field.Name;
+ instance.Name = @field.Name;
}
isDefined = true;
- return name;
+ return field;
}
+ private set => field = value;
}
///
@@ -182,7 +181,7 @@ public static TObject Parse(string obj)
/// Converts the instance to a human-readable representation.
///
/// A human-readable representation of the instance.
- public override string ToString() => name;
+ public override string ToString() => Name;
///
/// Determines whether the specified object is equal to the current object.
diff --git a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
index ce82315efa..9e840ebbac 100644
--- a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
+++ b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
@@ -29,8 +29,6 @@ public abstract class UnmanagedEnumClass : IComparable, IEquat
private static SortedList values;
private static bool isDefined;
- private string name;
-
///
/// Initializes a new instance of the class.
///
@@ -61,7 +59,7 @@ public string Name
get
{
if (isDefined)
- return name;
+ return field;
IEnumerable fields = typeof(TObject)
.GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
@@ -70,12 +68,13 @@ public string Name
foreach (FieldInfo @field in fields)
{
TObject instance = (TObject)@field.GetValue(null);
- instance.name = @field.Name;
+ instance.Name = @field.Name;
}
isDefined = true;
- return name;
+ return field;
}
+ private set => field = value;
}
///
@@ -204,7 +203,7 @@ public static TObject Parse(string obj)
/// Converts the instance to a human-readable representation.
///
/// A human-readable representation of the instance.
- public override string ToString() => name;
+ public override string ToString() => Name;
///
/// Determines whether the specified object is equal to the current object.
diff --git a/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs b/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs
index bce10ed688..badef6870b 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 => field;
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..e279719dc0 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 => field = value;
}
///
@@ -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 b19369208d..97d89e8d64 100644
--- a/EXILED/Exiled.API/Features/Generator.cs
+++ b/EXILED/Exiled.API/Features/Generator.cs
@@ -27,7 +27,6 @@ public class Generator : IWrapper, IWorldSpace
/// A of on the map.
///
internal static readonly Dictionary Scp079GeneratorToGenerator = new(new ComponentsEqualityComparer());
- private Room room;
///
/// Initializes a new instance of the class.
@@ -62,7 +61,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 b9788edb6c..e4ef78f072 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 6874edb59d..b297ffd5a3 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 => field;
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..20c1a2867b 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 => field;
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..632fe8cf8b 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 => field;
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 => field;
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 => field = value;
}
}
}
\ 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..289eba3d9c 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 => field;
+ 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..801498f351 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 => field;
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..5c67823430 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 => field;
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..c9ad7a6f7f 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 => field;
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..1b406dd274 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 => field = value;
}
///
diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs
index 086dd409bc..d9b3ae0b7e 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 => field;
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 => field;
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..9e4d82fe07 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 => field;
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 => field;
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 => field;
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..3b6f7367f7 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 => field;
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..78e71b0c2b 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 => field;
+ 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..e74917bca5 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 => field;
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..5e12f36438 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,19 @@ 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.
+ /// Gets or sets a value indicating whether the warhead can be started.
///
- public bool IsAuto { get; set; }
+ 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]
diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp559List.cs b/EXILED/Exiled.Events/Patches/Generic/Scp559List.cs
index 1bfb1a4f15..fad1fb6492 100644
--- a/EXILED/Exiled.Events/Patches/Generic/Scp559List.cs
+++ b/EXILED/Exiled.Events/Patches/Generic/Scp559List.cs
@@ -22,7 +22,7 @@ internal class Scp559List
{
private static void Postfix(Scp559Cake __instance)
{
- _ = new Scp559(__instance);
+ new Scp559(__instance);
}
}
}
\ No newline at end of file
From 0fcb87ebc6add77a849be463d74d198fd2fc89eb Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Sat, 17 Jan 2026 22:55:08 +0100
Subject: [PATCH 34/38] i miss that part :3
---
EXILED/Exiled.API/Features/Core/EActor.cs | 4 ++--
EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs | 2 +-
.../Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs | 2 +-
.../Features/Core/StateMachine/StateController.cs | 2 +-
.../Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs | 2 +-
EXILED/Exiled.API/Features/Player.cs | 2 +-
EXILED/Exiled.API/Features/Roles/FpcRole.cs | 2 +-
.../EventArgs/Cassie/SendingCassieMessageEventArgs.cs | 6 +++---
.../EventArgs/Map/ExplodingGrenadeEventArgs.cs | 2 +-
EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs | 2 +-
.../Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs | 2 +-
.../Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs | 2 +-
EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs | 2 +-
.../EventArgs/Player/InteractingShootingTargetEventArgs.cs | 4 ++--
EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs | 6 +++---
.../EventArgs/Player/ReservedSlotsCheckEventArgs.cs | 2 +-
.../EventArgs/Scp914/ChangingKnobSettingEventArgs.cs | 2 +-
.../EventArgs/Server/RespawningTeamEventArgs.cs | 2 +-
18 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/EXILED/Exiled.API/Features/Core/EActor.cs b/EXILED/Exiled.API/Features/Core/EActor.cs
index 0473bcb764..b051974788 100644
--- a/EXILED/Exiled.API/Features/Core/EActor.cs
+++ b/EXILED/Exiled.API/Features/Core/EActor.cs
@@ -97,7 +97,7 @@ public virtual Vector3 Scale
///
public virtual bool CanEverTick
{
- get => field;
+ get;
set
{
if (!IsEditable)
@@ -120,7 +120,7 @@ public virtual bool CanEverTick
///
public virtual float FixedTickRate
{
- get => field;
+ get;
set
{
if (!IsEditable)
diff --git a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
index 062a12487a..531c38a195 100644
--- a/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
+++ b/EXILED/Exiled.API/Features/Core/Generic/EnumClass.cs
@@ -74,7 +74,7 @@ public string Name
isDefined = true;
return field;
}
- private set => field = value;
+ private set;
}
///
diff --git a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
index 9e840ebbac..1c5413c7ac 100644
--- a/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
+++ b/EXILED/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs
@@ -74,7 +74,7 @@ public string Name
isDefined = true;
return field;
}
- private set => field = value;
+ private set;
}
///
diff --git a/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs b/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs
index badef6870b..ce193ec7a5 100644
--- a/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs
+++ b/EXILED/Exiled.API/Features/Core/StateMachine/StateController.cs
@@ -30,7 +30,7 @@ public abstract class StateController : EActor
///
public State CurrentState
{
- get => field;
+ get;
set
{
if (field.Id == value.Id)
diff --git a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs
index e279719dc0..6a9b8f4562 100644
--- a/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs
+++ b/EXILED/Exiled.API/Features/DamageHandlers/DamageHandlerBase.cs
@@ -76,7 +76,7 @@ public enum Action : byte
public virtual CassieAnnouncement CassieDeathAnnouncement
{
get => field ?? Base.CassieDeathAnnouncement;
- protected set => field = value;
+ protected set;
}
///
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index b297ffd5a3..7b752e6559 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -188,7 +188,7 @@ public Player(GameObject gameObject)
///
public ReferenceHub ReferenceHub
{
- get => field;
+ get;
private set
{
field = value ?? throw new NullReferenceException("Player's ReferenceHub cannot be null!");
diff --git a/EXILED/Exiled.API/Features/Roles/FpcRole.cs b/EXILED/Exiled.API/Features/Roles/FpcRole.cs
index 20c1a2867b..cf0cdae5c1 100644
--- a/EXILED/Exiled.API/Features/Roles/FpcRole.cs
+++ b/EXILED/Exiled.API/Features/Roles/FpcRole.cs
@@ -188,7 +188,7 @@ public bool MovementDetected
///
public bool IsUsingStamina
{
- get => field;
+ get;
set
{
if (!value)
diff --git a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs
index 632fe8cf8b..d5dbbef859 100644
--- a/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Cassie/SendingCassieMessageEventArgs.cs
@@ -80,7 +80,7 @@ public SendingCassieMessageEventArgs(CassieAnnouncement annc, bool isAllowed = t
///
public string CustomSubtitles
{
- get => field;
+ get;
set
{
if (field != value)
@@ -100,7 +100,7 @@ public string CustomSubtitles
///
public float GlitchScale
{
- get => field;
+ get;
set
{
if (!MakeNoise && value is not 0)
@@ -165,7 +165,7 @@ public CassieAnnouncement Announcement
_ => new CassieAnnouncement(newPayload, 0, GlitchScale / (API.Features.Warhead.IsDetonated ? 2F : 1F) * (MakeNoise ? 1F : 0F)),
};
}
- private set => field = value;
+ 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 289eba3d9c..474995b3b8 100644
--- a/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Map/ExplodingGrenadeEventArgs.cs
@@ -123,7 +123,7 @@ public ExplodingGrenadeEventArgs(Player thrower, EffectGrenade grenade, HashSet<
/// Explosion that are not from will return and can't be modified.
public ExplosionType ExplosionType
{
- get => field;
+ 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 801498f351..5679b15f76 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/BanningEventArgs.cs
@@ -38,7 +38,7 @@ public BanningEventArgs(Player target, Player issuer, ICommandSender commandSend
///
public long Duration
{
- get => field;
+ get;
set
{
if (field == value)
diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs
index 5c67823430..ee305c7ada 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingItemEventArgs.cs
@@ -41,7 +41,7 @@ public ChangingItemEventArgs(Player player, ItemBase newItem)
///
public Item Item
{
- get => field;
+ get;
set
{
if (value != null && !Player.Inventory.UserInventory.Items.TryGetValue(value.Serial, out _))
diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs
index c9ad7a6f7f..052fff48c6 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingRoleEventArgs.cs
@@ -64,7 +64,7 @@ public ChangingRoleEventArgs(Player player, RoleTypeId newRole, RoleChangeReason
///
public RoleTypeId NewRole
{
- get => field;
+ get;
set
{
InventoryRoleInfo inventory = value.GetInventory();
diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs
index 1b406dd274..7333b3b3f8 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs
@@ -54,7 +54,7 @@ public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeSc
public EscapeScenario EscapeScenario
{
get => (field is EscapeScenario.None && IsAllowed) ? EscapeScenario.CustomEscape : field;
- set => field = value;
+ set;
}
///
diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs
index d9b3ae0b7e..9555a12861 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingShootingTargetEventArgs.cs
@@ -70,7 +70,7 @@ public InteractingShootingTargetEventArgs(Player player, ShootingTarget shooting
///
public int NewMaxHp
{
- get => field;
+ get;
set
{
if (!ShootingTarget.IsSynced)
@@ -84,7 +84,7 @@ public int NewMaxHp
///
public int NewAutoResetTime
{
- get => field;
+ get;
set
{
if (!ShootingTarget.IsSynced)
diff --git a/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs
index 9e4d82fe07..3fc6c1221d 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/KickingEventArgs.cs
@@ -56,7 +56,7 @@ public KickingEventArgs(Player target, Player issuer, ICommandSender commandSend
///
public Player Target
{
- get => field;
+ get;
set
{
if (value is null || field == value)
@@ -84,7 +84,7 @@ public Player Target
///
public bool IsAllowed
{
- get => field;
+ get;
set
{
if (field == value)
@@ -102,7 +102,7 @@ public bool IsAllowed
///
public Player Player
{
- get => field;
+ get;
set
{
if (value is null || field == value)
diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs
index 3b6f7367f7..cb949391ca 100644
--- a/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Player/ReservedSlotsCheckEventArgs.cs
@@ -52,7 +52,7 @@ public ReservedSlotsCheckEventArgs(bool hasReservedSlot, string userId)
///
public ReservedSlotEventResult Result
{
- get => field;
+ get;
set
{
switch (value)
diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs
index 78e71b0c2b..4874f9de7b 100644
--- a/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Scp914/ChangingKnobSettingEventArgs.cs
@@ -42,7 +42,7 @@ public ChangingKnobSettingEventArgs(Player player, Scp914KnobSetting knobSetting
///
public Scp914KnobSetting KnobSetting
{
- get => field;
+ 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 e74917bca5..6e017b2816 100644
--- a/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Server/RespawningTeamEventArgs.cs
@@ -67,7 +67,7 @@ public RespawningTeamEventArgs(List players, int maxRespawn, SpawnableWa
///
public int MaximumRespawnAmount
{
- get => field;
+ get;
set
{
if (value < field)
From f67434da53490d54ab04251fdbd214863b1594a9 Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Sun, 18 Jan 2026 15:40:24 +0100
Subject: [PATCH 35/38] Oups
---
EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs
index 5e12f36438..214c8f8c2b 100644
--- a/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Warhead/StartingEventArgs.cs
@@ -23,10 +23,16 @@ public class StartingEventArgs : IPlayerEvent, IDeniableEvent
/// Indicating whether the event can be executed.
public StartingEventArgs(Player player, bool isAuto, bool isAllowed = true)
{
+ 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.
///
From ce441cc89fd76a7709408cc7e29bdb2724f10097 Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Mon, 16 Feb 2026 15:26:41 +0100
Subject: [PATCH 36/38] fix: EffectType.Blinded -> EffectType.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 449bc275bdaa9c0b8333314547bfbe868762e802 Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Thu, 19 Feb 2026 13:30:26 +0100
Subject: [PATCH 37/38] Apply suggestions from code review
---
EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/HazardList.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/LiftList.cs | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs b/EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs
index 6da34a65eb..d8cab19b53 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 d7eb560618..1761e27416 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 db16697730..34aacf25d1 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 d088dbe3ab..d4b212dcd5 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 e717ecad250a461ac5810e92457141686beb6790 Mon Sep 17 00:00:00 2001
From: Yamato <66829532+louis1706@users.noreply.github.com>
Date: Thu, 19 Feb 2026 18:18:47 +0100
Subject: [PATCH 38/38] Revert "Apply suggestions from code review"
This reverts commit 449bc275bdaa9c0b8333314547bfbe868762e802.
---
EXILED/Exiled.Events/Patches/Generic/AirlockListAdd.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/CoffeeListAdd.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/HazardList.cs | 2 +-
EXILED/Exiled.Events/Patches/Generic/LiftList.cs | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
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]