Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6585b64
refractor!: rename blindness effectype (#317)
MikeSus1 Feb 16, 2026
d728d1b
refactor!: ahp granting (#415)
TiBarification Feb 16, 2026
65a3033
feat: Bump Lib.Harmony from 2.2.2 to 2.4.2 in /EXILED (#755)
louis1706 Feb 16, 2026
f313ff9
feat: Speaker Api (#711)
MS-crew Feb 16, 2026
1c829c9
fix: Blinded -> Blindness
louis1706 Feb 16, 2026
59645fc
feat: add setter `Usable::IsUsing` (#753)
michcio15 Feb 19, 2026
0ef3c68
feat: buttons array (#752)
michcio15 Feb 19, 2026
be6d92d
feat!: More more c#14 (#756)
louis1706 Feb 20, 2026
6902224
fix: IsUsingStamina missing default set to True
louis1706 Mar 5, 2026
adfa2fb
fix: Players base Category Limits (#764)
MS-crew Mar 6, 2026
ab1f433
feat(warhead): Add ``IsOnCooldown`` property (#757)
Unbistrackted Feb 23, 2026
bec65a3
AllowsScp106 setter use C# 14
louis1706 Mar 8, 2026
41e9132
fix: `Door.AllowsScp106`
louis1706 Mar 8, 2026
0b968a7
fix: AdminToy.Position & Rotation ignoring parenting (#687)
Banalny-Banan Mar 16, 2026
3eea13e
nothing: Is this check better ? (#777)
louis1706 Mar 18, 2026
d82093d
fix: Replace new Locker instance creation with Get method (#779)
MS-crew Mar 18, 2026
053e328
feat: Add New Gate Type & Fix Some door not Gate (#776)
MS-crew Mar 18, 2026
eca6029
feat(warhead)!: ``Warhead.RemainingCooldown`` and ``WarheadStatus.OnC…
Unbistrackted Mar 28, 2026
90f5608
fix: Merge master to dev (#790)
louis1706 Mar 28, 2026
63c4f69
fix: PrefabHelper HczOneSided / HczTwoSided / HczBreakableDoor (#767)
louis1706 Mar 28, 2026
8f544a0
fix: Item leaks (#763)
MS-crew Mar 28, 2026
1cf9683
fix: SavingByAntiScp207 refactor the current game code & event not sa…
MS-crew Mar 29, 2026
b8c7639
chore(round)!: Change ``Round.IgnoredPlayers`` to use Player (#760)
Unbistrackted Mar 29, 2026
d3dd12a
feat: Added `Player::LifeIdentifier` (#766)
louis1706 Mar 29, 2026
f4b039f
fix: Nullable config (#769)
louis1706 Mar 29, 2026
46cf27c
fix: custom goggles (#768)
louis1706 Mar 29, 2026
dbd6c15
Merge branch 'master' into dev
louis1706 Mar 30, 2026
92d966c
fix: Custom Goggles not call OnRemovedGoggles when player dies (#794)
MS-crew Mar 30, 2026
6d07f72
Merge branch 'master' into dev
louis1706 Mar 31, 2026
c710ecf
Merge branch 'master' into dev
louis1706 Mar 31, 2026
6864fef
fix: PlayerReceivingLoadoutEventArgs from LabAPI (#773)
TiBarification Mar 31, 2026
73483b3
feat: Round starting event (#783)
louis1706 Apr 1, 2026
b7698cd
fix: nw bug 1560 and 1816 (#774)
louis1706 Apr 1, 2026
acb8bec
Merge branch 'master' into dev
louis1706 Apr 2, 2026
a1f8bf2
feat: Consuming Item Event (#805)
MS-crew Apr 14, 2026
696013d
feat: add generic singleton Instance pattern for CustomItem and Custo…
MS-crew Apr 29, 2026
5ad144e
feat: Speaker Api: Part Two (#762)
MS-crew Apr 29, 2026
6ae54e6
(MS forgor) Change Speaker channel from ReliableOrdered2 to Unreliabl…
MS-crew Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions EXILED/EXILED.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<PropertyGroup Condition="$(BuildProperties) == '' OR $(BuildProperties) == 'true'">
<TargetFramework>net48</TargetFramework>
<LangVersion>13.0</LangVersion>
<LangVersion>14.0</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>$(MSBuildThisFileDirectory)\bin\$(Configuration)\</OutputPath>
Expand All @@ -19,7 +19,7 @@
<!-- Enables public beta warning via the PUBLIC_BETA constant -->
<PublicBeta>false</PublicBeta>

<HarmonyVersion>2.2.2</HarmonyVersion>
<HarmonyVersion>2.4.2</HarmonyVersion>
<StyleCopVersion>1.1.118</StyleCopVersion>
<SemanticVersioningVersion>2.0.2</SemanticVersioningVersion>

Expand Down
6 changes: 3 additions & 3 deletions EXILED/Exiled.API/Enums/EffectType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public enum EffectType
Bleeding,

/// <summary>
/// Blurs the player's screen.
/// Make the player screen darker.
/// </summary>
Blinded,
Blindness,

/// <summary>
/// Increases damage the player receives. Does not apply any standalone damage.
Expand Down Expand Up @@ -256,7 +256,7 @@ public enum EffectType
PitDeath,

/// <summary>
/// <see cref="CustomPlayerEffects.Blurred"/>.
/// Blurs the player's screen.
/// </summary>
Blurred,

Expand Down
1 change: 1 addition & 0 deletions EXILED/Exiled.API/Enums/PrefabType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Exiled.API.Enums
/// <summary>
/// Type of prefab.
/// </summary>
/// <seealso cref="Features.PrefabHelper"/>
public enum PrefabType
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down
35 changes: 35 additions & 0 deletions EXILED/Exiled.API/Enums/SpeakerPlayMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="SpeakerPlayMode.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Enums
{
/// <summary>
/// Specifies the available modes for playing audio through a speaker.
/// </summary>
public enum SpeakerPlayMode : byte
{
/// <summary>
/// Play audio globally to all players.
/// </summary>
Global = 0,

/// <summary>
/// Play audio to a specific player.
/// </summary>
Player = 1,

/// <summary>
/// Play audio to a specific list of players.
/// </summary>
PlayerList = 2,

/// <summary>
/// Play audio to players matching a predicate.
/// </summary>
Predicate = 3,
}
}
16 changes: 12 additions & 4 deletions EXILED/Exiled.API/Enums/WarheadStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,38 @@

namespace Exiled.API.Enums
{
using System;

/// <summary>
/// All the available warhead statuses.
/// </summary>
/// <seealso cref="Features.Warhead.Status"/>
[Flags]
public enum WarheadStatus
{
/// <summary>
/// The warhead is not armed.
/// </summary>
NotArmed,
NotArmed = 0,

/// <summary>
/// The warhead is armed.
/// </summary>
Armed,
Armed = 1,

/// <summary>
/// The warhead detonation is in progress.
/// </summary>
InProgress,
InProgress = 2,

/// <summary>
/// The warhead has detonated.
/// </summary>
Detonated,
Detonated = 4,

/// <summary>
/// The warhead is on cooldown.
/// </summary>
OnCooldown = 8,
}
}
1 change: 1 addition & 0 deletions EXILED/Exiled.API/Exiled.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Reference Include="UnityEngine.PhysicsModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.PhysicsModule.dll" Private="false" />
<Reference Include="UnityEngine.AudioModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.AudioModule.dll" Private="false" />
<Reference Include="UnityEngine.UI" HintPath="$(EXILED_REFERENCES)\UnityEngine.UI.dll" Private="false" />
<Reference Include="UnityEngine.UnityWebRequestModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.UnityWebRequestModule.dll" Private="false" />
<Reference Include="YamlDotNet" HintPath="$(EXILED_REFERENCES)\YamlDotNet.dll" Private="false" />
<Reference Include="mscorlib" HintPath="$(EXILED_REFERENCES)\mscorlib.dll" Private="false" />
<Reference Include="System" HintPath="$(EXILED_REFERENCES)\System.dll" Private="false" />
Expand Down
4 changes: 2 additions & 2 deletions EXILED/Exiled.API/Extensions/EffectTypeExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static class EffectTypeExtension
{ EffectType.AmnesiaVision, typeof(AmnesiaVision) },
{ EffectType.Asphyxiated, typeof(Asphyxiated) },
{ EffectType.Bleeding, typeof(Bleeding) },
{ EffectType.Blinded, typeof(Blindness) },
{ EffectType.Blindness, typeof(Blindness) },
{ EffectType.BodyshotReduction, typeof(BodyshotReduction) },
{ EffectType.Burned, typeof(Burned) },
{ EffectType.CardiacArrest, typeof(CardiacArrest) },
Expand Down Expand Up @@ -194,7 +194,7 @@ or EffectType.Corroding or EffectType.Decontaminating or EffectType.Hemorrhage o
/// <returns>Whether the effect is a negative effect.</returns>
/// <seealso cref="IsHarmful(EffectType)"/>
public static bool IsNegative(this EffectType effect) => IsHarmful(effect) || effect is EffectType.AmnesiaItems
or EffectType.AmnesiaVision or EffectType.Blinded or EffectType.Burned or EffectType.Concussed or EffectType.Deafened
or EffectType.AmnesiaVision or EffectType.Blindness or EffectType.Burned or EffectType.Concussed or EffectType.Deafened
or EffectType.Disabled or EffectType.Ensnared or EffectType.Exhausted or EffectType.Flashed or EffectType.SinkHole
or EffectType.Stained or EffectType.InsufficientLighting or EffectType.SoundtrackMute or EffectType.Scanned or EffectType.Slowness;

Expand Down
9 changes: 3 additions & 6 deletions EXILED/Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ public static class MirrorExtensions
private static readonly ReadOnlyDictionary<Type, MethodInfo> ReadOnlyWriterExtensionsValue = new(WriterExtensionsValue);
private static readonly ReadOnlyDictionary<string, ulong> ReadOnlySyncVarDirtyBitsValue = new(SyncVarDirtyBitsValue);
private static readonly ReadOnlyDictionary<string, string> ReadOnlyRpcFullNamesValue = new(RpcFullNamesValue);
private static MethodInfo setDirtyBitsMethodInfoValue;
private static MethodInfo sendSpawnMessageMethodInfoValue;
private static string[] adminToyBaseSyncVarsValue;

/// <summary>
/// Gets <see cref="MethodInfo"/> corresponding to <see cref="Type"/>.
Expand Down Expand Up @@ -153,17 +150,17 @@ public static ReadOnlyDictionary<string, string> RpcFullNames
/// <summary>
/// Gets a <see cref="NetworkBehaviour.SetSyncVarDirtyBit(ulong)"/>'s <see cref="MethodInfo"/>.
/// </summary>
public static MethodInfo SetDirtyBitsMethodInfo => setDirtyBitsMethodInfoValue ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit));
public static MethodInfo SetDirtyBitsMethodInfo => field ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit));

/// <summary>
/// Gets a NetworkServer.SendSpawnMessage's <see cref="MethodInfo"/>.
/// </summary>
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);

/// <summary>
/// Gets all <see cref="AdminToyBase"/> sync var names.
/// </summary>
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();

/// <summary>
/// Plays a beep sound that only the target <paramref name="player"/> can hear.
Expand Down
213 changes: 213 additions & 0 deletions EXILED/Exiled.API/Features/Audio/AudioDataStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// -----------------------------------------------------------------------
// <copyright file="AudioDataStorage.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Features.Audio
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;

using Exiled.API.Structs.Audio;

using MEC;

using RoundRestarting;

using UnityEngine.Networking;

/// <summary>
/// Manages a global in-memory storage of decoded PCM audio data. Once stored, audio can be played using <see cref="PcmSources.CachedPcmSource"/>.
/// </summary>
public static class AudioDataStorage
{
static AudioDataStorage()
{
AudioStorage = new();
RoundRestart.OnRestartTriggered += OnRoundRestart;
}

/// <summary>
/// Gets the underlying storage, keyed by name.
/// </summary>
public static Dictionary<string, AudioData> AudioStorage { get; }

/// <summary>
/// Gets or sets a value indicating whether the storage is automatically cleared when a round restart is triggered.
/// </summary>
public static bool ClearOnRoundRestart { get; set; } = true;

/// <summary>
/// Loads and stores a local .wav file under the specified name.
/// </summary>
/// <param name="name">The unique storage key to assign to this audio.</param>
/// <param name="path">The absolute path to the local .wav file.</param>
/// <returns><c>true</c> if the file was successfully loaded and stored; otherwise, <c>false</c>.</returns>
public static bool AddWav(string name, string path)
{
if (!ValidateName(name))
return false;

if (AudioStorage.ContainsKey(name))
{
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping add.");
return false;
}

if (path.StartsWith("http"))
{
Log.Error($"[AudioDataStorage] '{path}' is a URL. Use AudioDataStorage.AddUrl() for web sources.");
return false;
}

if (!File.Exists(path))
{
Log.Error($"[AudioDataStorage] Local file not found: '{path}'");
return false;
}

try
{
AudioData parsed = WavUtility.WavToPcm(path);
return AudioStorage.TryAdd(name, parsed);
}
catch (Exception ex)
{
Log.Error($"[AudioDataStorage] Failed to load '{path}' into storage:\n{ex}");
return false;
}
}

/// <summary>
/// Stores raw PCM audio samples under the specified name.
/// </summary>
/// <param name="name">The unique storage key to assign.</param>
/// <param name="pcm">The raw PCM float array to store.</param>
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
public static bool Add(string name, float[] pcm)
{
if (pcm == null)
{
Log.Error($"[AudioDataStorage] Cannot store null array for key '{name}'.");
return false;
}

TrackData trackInfo = new()
{
Title = name,
Duration = (double)pcm.Length / VoiceChat.VoiceChatSettings.SampleRate,
};

return Add(name, new AudioData(pcm, trackInfo));
}

/// <summary>
/// Stores a fully constructed <see cref="AudioData"/> under the specified name.
/// </summary>
/// <param name="name">The unique storage key to assign.</param>
/// <param name="audioData">The <see cref="AudioData"/> to store.</param>
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
public static bool Add(string name, AudioData audioData)
{
if (!ValidateName(name))
return false;

if (audioData.Pcm == null || audioData.Pcm.Length == 0)
{
Log.Error($"[AudioDataStorage] AudioData for key '{name}' has null or empty PCM.");
return false;
}

if (AudioStorage.ContainsKey(name))
{
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping add.");
return false;
}

return AudioStorage.TryAdd(name, audioData);
}

/// <summary>
/// Starts an asynchronous download of a .wav file from the specified URL and adds it to the storage.
/// </summary>
/// <param name="name">The unique storage key to assign.</param>
/// <param name="url">The HTTP or HTTPS URL pointing to a valid .wav file.</param>
/// <returns>A <see cref="CoroutineHandle"/> for the running download coroutine.</returns>
public static CoroutineHandle AddWavUrl(string name, string url) => Timing.RunCoroutine(AddUrlCoroutine(name, url));

/// <summary>
/// Starts an asynchronous download of a .wav file from the specified URL and adds it to the storage.
/// </summary>
/// <param name="name">The unique storage key to assign.</param>
/// <param name="url">The HTTP or HTTPS URL pointing to a valid .wav file.</param>
/// <returns>A MEC-compatible <see cref="IEnumerator{T}"/> of <see cref="float"/>.</returns>
public static IEnumerator<float> AddUrlCoroutine(string name, string url)
{
if (!ValidateName(name))
yield break;

if (string.IsNullOrEmpty(url) || !url.StartsWith("http"))
{
Log.Error($"[AudioDataStorage] Invalid URL for key '{name}': '{url}'. Must start with http/https.");
yield break;
}

if (AudioStorage.ContainsKey(name))
{
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping download.");
yield break;
}

using UnityWebRequest www = UnityWebRequest.Get(url);
yield return Timing.WaitUntilDone(www.SendWebRequest());

if (www.result != UnityWebRequest.Result.Success)
{
Log.Error($"[AudioDataStorage] Download failed for '{url}': {www.error}");
yield break;
}

try
{
AudioData parsed = WavUtility.WavToPcm(www.downloadHandler.data);
parsed.TrackInfo.Path = url;
AudioStorage.TryAdd(name, parsed);
}
catch (Exception ex)
{
Log.Error($"[AudioDataStorage] Failed to parse downloaded WAV from '{url}':\n{ex}");
}
}

/// <summary>
/// Removes a stored audio entry by name.
/// </summary>
/// <param name="name">The storage name/key to remove.</param>
/// <returns><c>true</c> if the entry was found and removed; otherwise, <c>false</c>.</returns>
public static bool Remove(string name) => AudioStorage.Remove(name, out _);

/// <summary>
/// Clears all entries from the audio storage, freeing all associated memory.
/// </summary>
public static void Clear() => AudioStorage.Clear();

private static bool ValidateName(string name)
{
if (!string.IsNullOrEmpty(name))
return true;

Log.Error("[AudioDataStorage] Storage name (key) cannot be null or empty.");
return false;
}

private static void OnRoundRestart()
{
if (ClearOnRoundRestart)
Clear();
}
}
}
Loading
Loading