From 73f4de2b178a99844f9da6321da037378cbbbe99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 2 Jun 2026 20:27:06 +0300 Subject: [PATCH 01/28] Decals --- EXILED/Exiled.API/Features/Items/Firearm.cs | 16 +++ EXILED/Exiled.API/Features/Map.cs | 122 ++++++++++++++++++++ EXILED/Exiled.API/Features/Player.cs | 73 ++++++++++++ 3 files changed, 211 insertions(+) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index b97a8673bd..ac2d934f4a 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -12,6 +12,7 @@ namespace Exiled.API.Features.Items using System.Linq; using CameraShaking; + using Enums; using Exiled.API.Features.Items.FirearmModules; @@ -20,12 +21,18 @@ namespace Exiled.API.Features.Items using Exiled.API.Features.Pickups; using Exiled.API.Interfaces; using Exiled.API.Structs; + using Extensions; + + using InventorySystem; + using InventorySystem.Items; using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; + using UnityEngine; + using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; @@ -75,6 +82,10 @@ public Firearm(BaseFirearm itemBase) AnimatorReloaderModule = animatorReloaderModule; break; + case ImpactEffectsModule impactEffectsModule: + ImpactEffectsModule = impactEffectsModule; + break; + default: break; } @@ -151,6 +162,11 @@ public static IReadOnlyDictionary public AnimatorReloaderModuleBase AnimatorReloaderModule { get; } + /// + /// Gets an impact effects module for the current firearm. + /// + public ImpactEffectsModule ImpactEffectsModule { get; } + /// /// Gets or sets the amount of ammo in the firearm magazine. /// diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index c4feacd0cb..b94cfff78c 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -15,22 +15,40 @@ namespace Exiled.API.Features using System.Linq; using CommandSystem.Commands.RemoteAdmin.Cleanup; + using Decals; + using Enums; + using Exiled.API.Extensions; using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups; using Interactables.Interobjects; + using InventorySystem; + using InventorySystem.Items; + using InventorySystem.Items.Autosync; + using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Pickups; using InventorySystem.Items.ThrowableProjectiles; + using Items; + using LightContainmentZoneDecontamination; + using MapGeneration; + + using Mirror; + using PlayerRoles.Ragdolls; + + using RelativePositioning; + using RemoteAdmin; + using UnityEngine; + using Utils; using Utils.Networking; @@ -436,6 +454,110 @@ public static void CleanAllRagdolls(IEnumerable ragDolls) [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] public static void PlaceBlood(Vector3 position, Vector3 direction) => _ = 0; + /// + /// Spawns a blood decal. + /// + /// The position of the blood decal. + /// The source position of the blood decal. + /// if the blood decal was successfully spawned; otherwise, . + public static bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); + + /// + /// Spawns a blood decal for the specified players. + /// + /// The players for which to spawn the blood decal. + /// The position of the blood decal. + /// The source position of the blood decal. + /// if the blood decal was successfully spawned; otherwise, . + public static bool SpawnBlood(IEnumerable players, Vector3 position, Vector3 sourcePosition) => SpawnDecal(players, position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); + + /// + /// Spawns a decal. + /// + /// The position of the decal. + /// The source position of the decal. + /// The . + /// The to use. + /// if the decal was successfully spawned; otherwise, . + public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) + { + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + Firearm firearm = (Firearm)Item.Get(itemBase); + if (firearm == null) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; + if (impactEffectsModule == null) + { + Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); + return false; + } + + using (new AutosyncRpc(impactEffectsModule.ItemId, out NetworkWriter writer)) + { + writer.WriteByte(impactEffectsModule.SyncId); + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)decalType); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(sourcePosition)); + } + + return true; + } + + /// + /// Spawns a decal for the specified targets. + /// + /// The targets for which to spawn the decal. + /// The position of the decal. + /// The source position of the decal. + /// The . + /// The to use. + /// if the decal was successfully spawned; otherwise, . + public static bool SpawnDecal(IEnumerable targets, Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) + { + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + Firearm firearm = (Firearm)Item.Get(itemBase); + if (firearm == null) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; + if (impactEffectsModule == null) + { + Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); + return false; + } + + HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); + + using (new AutosyncRpc(impactEffectsModule.ItemId, hub => targetHubs.Contains(hub), out NetworkWriter writer)) + { + writer.WriteByte(impactEffectsModule.SyncId); + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)decalType); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(sourcePosition)); + } + + return true; + } + /// /// Gets all the near cameras. /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 97b3a1b2a7..7d21bbb604 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -15,10 +15,16 @@ namespace Exiled.API.Features using System.Runtime.CompilerServices; using Core; + using CustomPlayerEffects; using CustomPlayerEffects.Danger; + using DamageHandlers; + + using Decals; + using Enums; + using Exiled.API.Features.Core.Interfaces; using Exiled.API.Features.CustomStats; using Exiled.API.Features.Doors; @@ -29,25 +35,36 @@ namespace Exiled.API.Features using Exiled.API.Features.Roles; using Exiled.API.Interfaces; using Exiled.API.Structs; + using Extensions; + using Footprinting; + using global::Scp914; + using Hints; + using Interactables.Interobjects; + using InventorySystem; using InventorySystem.Disarming; using InventorySystem.Items; using InventorySystem.Items.Armor; + using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Firearms.ShotEvents; using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp330; + using MapGeneration.Distributors; using MapGeneration.Rooms; + using MEC; + using Mirror; using Mirror.LiteNetLib4Mirror; + using PlayerRoles; using PlayerRoles.FirstPersonControl; using PlayerRoles.FirstPersonControl.Thirdperson; @@ -56,16 +73,22 @@ namespace Exiled.API.Features using PlayerRoles.RoleAssign; using PlayerRoles.Spectating; using PlayerRoles.Voice; + using PlayerStatsSystem; + using RelativePositioning; + using RemoteAdmin; + using RoundRestarting; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; + using Utils; using Utils.Networking; + using VoiceChat; using VoiceChat.Playbacks; @@ -3845,6 +3868,56 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); + /// + /// Spawns a blood decal for this player. + /// + /// The position of the blood decal. + /// The source position of the blood decal. + /// if the blood decal was successfully spawned; otherwise, . + public bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood); + + /// + /// Spawns a decal for this player. + /// + /// The position of the decal. + /// The source position of the decal. + /// The . + /// The to use. + /// if the decal was successfully spawned; otherwise, . + public bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) + { + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + Firearm firearm = (Firearm)Item.Get(itemBase); + if (firearm == null) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; + if (impactEffectsModule == null) + { + Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); + return false; + } + + using (new AutosyncRpc(impactEffectsModule.ItemId, ReferenceHub, out NetworkWriter writer)) + { + writer.WriteByte(impactEffectsModule.SyncId); + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)decalType); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(sourcePosition)); + } + + return true; + } + /// public IEnumerable GetNearCameras(float toleration = 15f) => Map.GetNearCameras(Position, toleration); From 70c2e5b65107c92e50fe94c81294ab22180115c9 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:47:53 +0300 Subject: [PATCH 02/28] Update EXILED/Exiled.API/Features/Map.cs Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Map.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index b94cfff78c..36b2ad32e5 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -487,7 +487,7 @@ public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoo return false; } - Firearm firearm = (Firearm)Item.Get(itemBase); + Firearm firearm = Item.Get(itemBase); if (firearm == null) { Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); From ae154b2682a9a330fdd4b011bd51633fbe295fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Wed, 3 Jun 2026 22:52:49 +0300 Subject: [PATCH 03/28] consitent --- EXILED/Exiled.API/Features/Map.cs | 4 ++-- EXILED/Exiled.API/Features/Player.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index b94cfff78c..4eed317bf4 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -487,7 +487,7 @@ public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoo return false; } - Firearm firearm = (Firearm)Item.Get(itemBase); + Firearm firearm = Item.Get(itemBase); if (firearm == null) { Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); @@ -530,7 +530,7 @@ public static bool SpawnDecal(IEnumerable targets, Vector3 position, Vec return false; } - Firearm firearm = (Firearm)Item.Get(itemBase); + Firearm firearm = Item.Get(itemBase); if (firearm == null) { Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 7d21bbb604..70b8e19367 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3892,7 +3892,7 @@ public bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoolType d return false; } - Firearm firearm = (Firearm)Item.Get(itemBase); + Firearm firearm = Item.Get(itemBase); if (firearm == null) { Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); From 7f357293d749af9878adc0d07de42e6c4375c072 Mon Sep 17 00:00:00 2001 From: XingYeFish <154997195+XingYeNotFish@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:20:26 +0800 Subject: [PATCH 04/28] refactor: CustomScpTermination (#629) * refactor: CustomScpTermination * for new cassie update * fix: Remove breaking change --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> --- EXILED/Exiled.API/Features/Cassie.cs | 59 ++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/EXILED/Exiled.API/Features/Cassie.cs b/EXILED/Exiled.API/Features/Cassie.cs index 475e68ad4e..4fbfb3647b 100644 --- a/EXILED/Exiled.API/Features/Cassie.cs +++ b/EXILED/Exiled.API/Features/Cassie.cs @@ -21,8 +21,7 @@ namespace Exiled.API.Features using Respawning; using Respawning.NamingRules; - using CustomFirearmHandler = DamageHandlers.FirearmDamageHandler; - using CustomHandlerBase = DamageHandlers.DamageHandlerBase; + using CustomHandler = DamageHandlers.CustomDamageHandler; /// /// A set of tools to use in-game C.A.S.S.I.E. @@ -188,25 +187,51 @@ public static void ScpTermination(Player scp, DamageHandlerBase info) => CassieScpTerminationAnnouncement.AnnounceScpTermination(scp.ReferenceHub, info); /// - /// Announces the termination of a custom SCP name. + /// Announces the termination of a custom SCP Number. /// - /// SCP Name. Note that for larger numbers, C.A.S.S.I.E will pronounce the place (eg. "457" -> "four hundred fifty seven"). Spaces can be used to prevent this behavior. + /// SCP Number. Note that for larger numbers, C.A.S.S.I.E will pronounce the place (eg. "457" -> "four hundred fifty seven"). Spaces can be used to prevent this behavior. /// Hit Information. - public static void CustomScpTermination(string scpName, CustomHandlerBase info) + [Obsolete("Use this instead CustomScpTermination(string, CustomHandler)")] + public static void CustomScpTermination(string scpNumber, DamageHandlers.DamageHandlerBase info) { - string result = scpName; - if (info.Is(out MicroHidDamageHandler _)) - result += " SUCCESSFULLY TERMINATED BY AUTOMATIC SECURITY SYSTEM"; - else if (info.Is(out WarheadDamageHandler _)) - result += " SUCCESSFULLY TERMINATED BY ALPHA WARHEAD"; - else if (info.Is(out UniversalDamageHandler _)) - result += " LOST IN DECONTAMINATION SEQUENCE"; - else if (info.BaseIs(out CustomFirearmHandler firearmDamageHandler) && firearmDamageHandler.Attacker is Player attacker) - result += " CONTAINEDSUCCESSFULLY " + ConvertTeam(attacker.Role.Team, attacker.UnitName); - - // result += "To be changed"; + if (scpNumber.StartsWith("SCP", StringComparison.InvariantCultureIgnoreCase)) + scpNumber = scpNumber.Remove(0, 3); + + if (info is CustomHandler customHandler) + CustomScpTermination(scpNumber, customHandler); + } + + /// + /// Announces the termination of a custom SCP Number. + /// + /// SCP Number. Note that for larger numbers, C.A.S.S.I.E will pronounce the place (eg. "457" -> "four hundred fifty seven"). Spaces can be used to prevent this behavior. + /// Hit Information. + public static void CustomScpTermination(string scpNumber, CustomHandler info) + { + if (info is null) + throw new System.ArgumentNullException(nameof(info)); + + string result = "SCP " + scpNumber; + if (info.Attacker is null) + { + result += info.Type switch + { + Enums.DamageType.Warhead => " SUCCESSFULLY TERMINATED BY ALPHA WARHEAD", + Enums.DamageType.Decontamination => " LOST IN DECONTAMINATION SEQUENCE", + Enums.DamageType.Tesla => " SUCCESSFULLY TERMINATED BY AUTOMATIC SECURITY SYSTEM", + _ => " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED", + }; + } else - result += " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED"; + { + result += info.Attacker.Role.Team switch + { + Team.SCPs => " TERMINATED BY SCP " + string.Join(" ", info.Attacker.Role.Name.Remove(0, 4).ToCharArray()), + Team.Flamingos => " TERMINATED BY SCP 1 5 0 7", + Team.OtherAlive or Team.Dead => " SUCCESSFULLY TERMINATED . TERMINATION CAUSE UNSPECIFIED", + _ => " CONTAINEDSUCCESSFULLY " + ConvertTeam(info.Attacker.Role.Team, info.Attacker.UnitName), + }; + } float num = AlphaWarheadController.TimeUntilDetonation <= 0f ? 3.5f : 1f; GlitchyMessage(result, UnityEngine.Random.Range(0.1f, 0.14f) * num, UnityEngine.Random.Range(0.07f, 0.08f) * num); From c91697c4a7c9dad7d407072efa0e94e38ecb83aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Thu, 4 Jun 2026 20:58:52 +0300 Subject: [PATCH 05/28] obsulutes --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 1 + EXILED/Exiled.API/Features/Map.cs | 4 ++-- EXILED/Exiled.API/Features/Player.cs | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 2f8750cc34..7bf75549bd 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -265,6 +265,7 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp /// The direction of the blood decal. /// The RoleTypeId from who blood come from. /// The sound than player get when getting shot. + [Obsolete("Use Player::SpawnBlood(Vector3, Vector3) instead.")] public static void PlaceBlood(this Player player, Vector3 position, Vector3 origin, RoleTypeId roleTypeId, int gettingShotSoundIndex) { if (!roleTypeId.TryGetRoleBase(out PlayerRoleBase playerRoleBase) || playerRoleBase is not IBleedableRole) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 4eed317bf4..8bdf989dfc 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -451,8 +451,8 @@ public static void CleanAllRagdolls(IEnumerable ragDolls) /// /// The position of the blood decal. /// The direction of the blood decal. - [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] - public static void PlaceBlood(Vector3 position, Vector3 direction) => _ = 0; + [Obsolete("Use SpawnBlood(Vector3, Vector3) instead.")] + public static void PlaceBlood(Vector3 position, Vector3 direction) => SpawnBlood(position, direction); /// /// Spawns a blood decal. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 70b8e19367..908dd8c8f8 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3864,9 +3864,9 @@ public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(Position, itemType, pitch, clipIndex); - /// - [Obsolete("Use PlaceBlood(this Player, Vector3, Vector3, RoleTypeId, int) instead.")] - public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); + /// + [Obsolete("Use Player::SpawnBlood(Vector3, Vector3) instead.")] + public void PlaceBlood(Vector3 direction) => SpawnBlood(Position, direction); /// /// Spawns a blood decal for this player. From af2ec1a08724c4a334902f89c5e3d21d1d7bdc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Thu, 4 Jun 2026 23:27:59 +0300 Subject: [PATCH 06/28] directly usin rpcs --- EXILED/Exiled.API/Features/Map.cs | 10 ++++------ EXILED/Exiled.API/Features/Player.cs | 5 ++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 8bdf989dfc..1cd9039757 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -501,14 +501,13 @@ public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoo return false; } - using (new AutosyncRpc(impactEffectsModule.ItemId, out NetworkWriter writer)) + impactEffectsModule.SendRpc(writer => { - writer.WriteByte(impactEffectsModule.SyncId); writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); writer.WriteByte((byte)decalType); writer.WriteRelativePosition(new RelativePosition(position)); writer.WriteRelativePosition(new RelativePosition(sourcePosition)); - } + }); return true; } @@ -546,14 +545,13 @@ public static bool SpawnDecal(IEnumerable targets, Vector3 position, Vec HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - using (new AutosyncRpc(impactEffectsModule.ItemId, hub => targetHubs.Contains(hub), out NetworkWriter writer)) + impactEffectsModule.SendRpc(targetHubs.Contains, writer => { - writer.WriteByte(impactEffectsModule.SyncId); writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); writer.WriteByte((byte)decalType); writer.WriteRelativePosition(new RelativePosition(position)); writer.WriteRelativePosition(new RelativePosition(sourcePosition)); - } + }); return true; } diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 908dd8c8f8..6ec8405a85 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3906,14 +3906,13 @@ public bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoolType d return false; } - using (new AutosyncRpc(impactEffectsModule.ItemId, ReferenceHub, out NetworkWriter writer)) + impactEffectsModule.SendRpc(ReferenceHub, writer => { - writer.WriteByte(impactEffectsModule.SyncId); writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); writer.WriteByte((byte)decalType); writer.WriteRelativePosition(new RelativePosition(position)); writer.WriteRelativePosition(new RelativePosition(sourcePosition)); - } + }); return true; } From a9d58b244ac4ea42b92046e6825aaec89fa360f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 6 Jun 2026 01:50:43 +0300 Subject: [PATCH 07/28] fix increment & helper methods --- .../API/Features/CustomRole.cs | 77 +++++++++++++++++++ .../Events/PlayerHandlers.cs | 30 ++------ 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index f2dd9bc8d8..9894ec264a 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -54,6 +54,11 @@ public abstract class CustomRole /// public static HashSet Registered { get; } = new(); + /// + /// Gets or sets a value indicating whether the role is enabled. + /// + public virtual bool IsEnabled { get; set; } = true; + /// /// Gets or sets the custom RoleID of the role. /// @@ -323,6 +328,39 @@ public static IEnumerable RegisterRoles(bool byAttribute = false) return roles; } + /// + /// Registers all the 's present in the current plugin's config. + /// + /// The source containing the custom roles. + /// An optional collection of s to ignore during registration. + /// A of which contains all registered 's. + public static IEnumerable RegisterRolesFromSource(object source, IEnumerable? ignoredRoles = null) + { + List roles = new(); + + if (source == null) + return roles; + + PropertyInfo[] properties = source.GetType().GetProperties(); + + foreach (PropertyInfo property in properties) + { + if (!typeof(CustomRole).IsAssignableFrom(property.PropertyType)) + continue; + + if (property.GetValue(source) is not CustomRole configRole) + continue; + + if (ignoredRoles != null && ignoredRoles.Any(ignored => ignored.GetType() == configRole.GetType())) + continue; + + if (configRole.TryRegister()) + roles.Add(configRole); + } + + return roles; + } + /// /// Registers all the 's present in the current assembly. /// @@ -486,6 +524,39 @@ public static IEnumerable UnregisterRoles(IEnumerable targetTy /// A of which contains all unregistered 's. public static IEnumerable UnregisterRoles(IEnumerable targetRoles, bool isIgnored = false) => UnregisterRoles(targetRoles.Select(x => x.GetType()), isIgnored); + /// + /// Unregisters all the 's present in the current plugin's config. + /// + /// The source containing the target roles. + /// An optional collection of s to ignore during unregisteration. + /// A of which contains all unregistered 's. + public static IEnumerable UnregisterFromSource(object source, IEnumerable? ignoredRoles = null) + { + List roles = new(); + + if (source == null) + return roles; + + PropertyInfo[] properties = source.GetType().GetProperties(); + + foreach (PropertyInfo property in properties) + { + if (!typeof(CustomRole).IsAssignableFrom(property.PropertyType)) + continue; + + if (property.GetValue(source) is not CustomRole configRole) + continue; + + if (ignoredRoles != null && ignoredRoles.Any(ignored => ignored.GetType() == configRole.GetType())) + continue; + + if (configRole.TryUnregister()) + roles.Add(configRole); + } + + return roles; + } + /// /// ResyncCustomRole Friendly Fire with Player (Append, or Overwrite). /// @@ -810,6 +881,12 @@ internal bool TryRegister() if (!CustomRoles.Instance!.Config.IsEnabled) return false; + if (!IsEnabled) + { + Log.Debug($"Custom role {Name} is not enabled and will not be registered."); + return false; + } + if (!Registered.Contains(this)) { if (Registered.Any(r => r.Id == Id)) diff --git a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs index 47a153221d..d637dbd0e3 100644 --- a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs +++ b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs @@ -17,6 +17,8 @@ namespace Exiled.CustomRoles.Events using Exiled.CustomRoles.API.Features; using Exiled.Events.EventArgs.Player; + using UnityEngine; + /// /// Handles general events for players. /// @@ -66,9 +68,7 @@ internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) internal void OnSpawned(SpawnedEventArgs ev) { if (!ValidSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) - { return; - } float totalChance = 0f; List eligibleRoles = new(8); @@ -83,17 +83,13 @@ internal void OnSpawned(SpawnedEventArgs ev) } if (eligibleRoles.Count == 0) - { return; - } - float lotterySize = Math.Max(100f, totalChance); + float lotterySize = Mathf.Max(100f, totalChance); float randomRoll = (float)Loader.Loader.Random.NextDouble() * lotterySize; if (randomRoll >= totalChance) - { return; - } foreach (CustomRole candidateRole in eligibleRoles) { @@ -103,23 +99,9 @@ internal void OnSpawned(SpawnedEventArgs ev) continue; } - if (candidateRole.SpawnProperties is null) - { - candidateRole.AddRole(ev.Player); - break; - } - - int newSpawnCount = candidateRole.SpawnedPlayers++; - if (newSpawnCount <= candidateRole.SpawnProperties.Limit) - { - candidateRole.AddRole(ev.Player); - break; - } - else - { - candidateRole.SpawnedPlayers--; - randomRoll -= candidateRole.SpawnChance; - } + candidateRole.SpawnedPlayers++; + candidateRole.AddRole(ev.Player); + break; } } } From 9c075f27a32f673b2e7ec399734b8a7384003d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 6 Jun 2026 01:54:48 +0300 Subject: [PATCH 08/28] Revert "fix increment & helper methods" This reverts commit a9d58b244ac4ea42b92046e6825aaec89fa360f9. --- .../API/Features/CustomRole.cs | 77 ------------------- .../Events/PlayerHandlers.cs | 30 ++++++-- 2 files changed, 24 insertions(+), 83 deletions(-) diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 9894ec264a..f2dd9bc8d8 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -54,11 +54,6 @@ public abstract class CustomRole /// public static HashSet Registered { get; } = new(); - /// - /// Gets or sets a value indicating whether the role is enabled. - /// - public virtual bool IsEnabled { get; set; } = true; - /// /// Gets or sets the custom RoleID of the role. /// @@ -328,39 +323,6 @@ public static IEnumerable RegisterRoles(bool byAttribute = false) return roles; } - /// - /// Registers all the 's present in the current plugin's config. - /// - /// The source containing the custom roles. - /// An optional collection of s to ignore during registration. - /// A of which contains all registered 's. - public static IEnumerable RegisterRolesFromSource(object source, IEnumerable? ignoredRoles = null) - { - List roles = new(); - - if (source == null) - return roles; - - PropertyInfo[] properties = source.GetType().GetProperties(); - - foreach (PropertyInfo property in properties) - { - if (!typeof(CustomRole).IsAssignableFrom(property.PropertyType)) - continue; - - if (property.GetValue(source) is not CustomRole configRole) - continue; - - if (ignoredRoles != null && ignoredRoles.Any(ignored => ignored.GetType() == configRole.GetType())) - continue; - - if (configRole.TryRegister()) - roles.Add(configRole); - } - - return roles; - } - /// /// Registers all the 's present in the current assembly. /// @@ -524,39 +486,6 @@ public static IEnumerable UnregisterRoles(IEnumerable targetTy /// A of which contains all unregistered 's. public static IEnumerable UnregisterRoles(IEnumerable targetRoles, bool isIgnored = false) => UnregisterRoles(targetRoles.Select(x => x.GetType()), isIgnored); - /// - /// Unregisters all the 's present in the current plugin's config. - /// - /// The source containing the target roles. - /// An optional collection of s to ignore during unregisteration. - /// A of which contains all unregistered 's. - public static IEnumerable UnregisterFromSource(object source, IEnumerable? ignoredRoles = null) - { - List roles = new(); - - if (source == null) - return roles; - - PropertyInfo[] properties = source.GetType().GetProperties(); - - foreach (PropertyInfo property in properties) - { - if (!typeof(CustomRole).IsAssignableFrom(property.PropertyType)) - continue; - - if (property.GetValue(source) is not CustomRole configRole) - continue; - - if (ignoredRoles != null && ignoredRoles.Any(ignored => ignored.GetType() == configRole.GetType())) - continue; - - if (configRole.TryUnregister()) - roles.Add(configRole); - } - - return roles; - } - /// /// ResyncCustomRole Friendly Fire with Player (Append, or Overwrite). /// @@ -881,12 +810,6 @@ internal bool TryRegister() if (!CustomRoles.Instance!.Config.IsEnabled) return false; - if (!IsEnabled) - { - Log.Debug($"Custom role {Name} is not enabled and will not be registered."); - return false; - } - if (!Registered.Contains(this)) { if (Registered.Any(r => r.Id == Id)) diff --git a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs index d637dbd0e3..47a153221d 100644 --- a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs +++ b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs @@ -17,8 +17,6 @@ namespace Exiled.CustomRoles.Events using Exiled.CustomRoles.API.Features; using Exiled.Events.EventArgs.Player; - using UnityEngine; - /// /// Handles general events for players. /// @@ -68,7 +66,9 @@ internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) internal void OnSpawned(SpawnedEventArgs ev) { if (!ValidSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) + { return; + } float totalChance = 0f; List eligibleRoles = new(8); @@ -83,13 +83,17 @@ internal void OnSpawned(SpawnedEventArgs ev) } if (eligibleRoles.Count == 0) + { return; + } - float lotterySize = Mathf.Max(100f, totalChance); + float lotterySize = Math.Max(100f, totalChance); float randomRoll = (float)Loader.Loader.Random.NextDouble() * lotterySize; if (randomRoll >= totalChance) + { return; + } foreach (CustomRole candidateRole in eligibleRoles) { @@ -99,9 +103,23 @@ internal void OnSpawned(SpawnedEventArgs ev) continue; } - candidateRole.SpawnedPlayers++; - candidateRole.AddRole(ev.Player); - break; + if (candidateRole.SpawnProperties is null) + { + candidateRole.AddRole(ev.Player); + break; + } + + int newSpawnCount = candidateRole.SpawnedPlayers++; + if (newSpawnCount <= candidateRole.SpawnProperties.Limit) + { + candidateRole.AddRole(ev.Player); + break; + } + else + { + candidateRole.SpawnedPlayers--; + randomRoll -= candidateRole.SpawnChance; + } } } } From eccf7d73f731116e1898e57a39dc3a18d7f7d3ea Mon Sep 17 00:00:00 2001 From: Yamato <66829532+louis1706@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:22:24 +0200 Subject: [PATCH 09/28] suggestion --- EXILED/Exiled.API/Features/Map.cs | 7 +++++-- EXILED/Exiled.API/Features/Player.cs | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 1cd9039757..34833c92f2 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -451,8 +451,11 @@ public static void CleanAllRagdolls(IEnumerable ragDolls) /// /// The position of the blood decal. /// The direction of the blood decal. - [Obsolete("Use SpawnBlood(Vector3, Vector3) instead.")] - public static void PlaceBlood(Vector3 position, Vector3 direction) => SpawnBlood(position, direction); + public static void PlaceBlood(Vector3 position, Vector3 direction) + { + if (Physics.Raycast(position, direction, out RaycastHit hitInfo, ImpactEffectsModule.ReceivingLayers)) + SpawnBlood(hitInfo.point + (hitInfo.normal * Decal.SurfaceDistance), -hitInfo.normal); + } /// /// Spawns a blood decal. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 6ec8405a85..2a14aefbbd 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3864,8 +3864,7 @@ public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(Position, itemType, pitch, clipIndex); - /// - [Obsolete("Use Player::SpawnBlood(Vector3, Vector3) instead.")] + /// public void PlaceBlood(Vector3 direction) => SpawnBlood(Position, direction); /// From e488ff94863b5dedfbef7ad6e99b2cbc5a3e3ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 6 Jun 2026 21:47:18 +0300 Subject: [PATCH 10/28] update --- EXILED/Exiled.API/Features/Map.cs | 14 +++++++------- EXILED/Exiled.API/Features/Player.cs | 15 +++++++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index 34833c92f2..a29fb1d21f 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -447,10 +447,10 @@ public static void CleanAllRagdolls(IEnumerable ragDolls) public static void Clean(DecalPoolType decalType) => Clean(decalType, int.MaxValue); /// - /// Places a blood decal. + /// Places a blood decal using a raycast. /// - /// The position of the blood decal. - /// The direction of the blood decal. + /// The origin position of the raycast. + /// The direction in which the raycast is fired to detect a surface. public static void PlaceBlood(Vector3 position, Vector3 direction) { if (Physics.Raycast(position, direction, out RaycastHit hitInfo, ImpactEffectsModule.ReceivingLayers)) @@ -461,7 +461,7 @@ public static void PlaceBlood(Vector3 position, Vector3 direction) /// Spawns a blood decal. /// /// The position of the blood decal. - /// The source position of the blood decal. + /// The raycast origin used to determine the decal's orientation. /// if the blood decal was successfully spawned; otherwise, . public static bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); @@ -470,7 +470,7 @@ public static void PlaceBlood(Vector3 position, Vector3 direction) /// /// The players for which to spawn the blood decal. /// The position of the blood decal. - /// The source position of the blood decal. + /// The raycast origin used to determine the decal's orientation. /// if the blood decal was successfully spawned; otherwise, . public static bool SpawnBlood(IEnumerable players, Vector3 position, Vector3 sourcePosition) => SpawnDecal(players, position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); @@ -478,7 +478,7 @@ public static void PlaceBlood(Vector3 position, Vector3 direction) /// Spawns a decal. /// /// The position of the decal. - /// The source position of the decal. + /// The raycast origin used to determine the decal's orientation. /// The . /// The to use. /// if the decal was successfully spawned; otherwise, . @@ -520,7 +520,7 @@ public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoo /// /// The targets for which to spawn the decal. /// The position of the decal. - /// The source position of the decal. + /// The raycast origin used to determine the decal's orientation. /// The . /// The to use. /// if the decal was successfully spawned; otherwise, . diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 2a14aefbbd..47e69bcf25 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3864,14 +3864,21 @@ public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(Position, itemType, pitch, clipIndex); - /// - public void PlaceBlood(Vector3 direction) => SpawnBlood(Position, direction); + /// + /// Place a blood decal for this player using a raycast from the player's position. + /// + /// The direction in which the raycast is fired to detect a surface. + public void PlaceBlood(Vector3 direction) + { + if (Physics.Raycast(Position, direction, out RaycastHit hitInfo, ImpactEffectsModule.ReceivingLayers)) + SpawnBlood(hitInfo.point + (hitInfo.normal * Decal.SurfaceDistance), -hitInfo.normal); + } /// /// Spawns a blood decal for this player. /// /// The position of the blood decal. - /// The source position of the blood decal. + /// The raycast origin used to determine the decal's orientation. /// if the blood decal was successfully spawned; otherwise, . public bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood); @@ -3879,7 +3886,7 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = /// Spawns a decal for this player. /// /// The position of the decal. - /// The source position of the decal. + /// The raycast origin used to determine the decal's orientation. /// The . /// The to use. /// if the decal was successfully spawned; otherwise, . From c768459e6e306a820df7b69b8f230733d88cdc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sun, 7 Jun 2026 16:25:00 +0300 Subject: [PATCH 11/28] fix --- EXILED/Exiled.API/Features/Player.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 47e69bcf25..ce5a388c20 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3864,15 +3864,8 @@ public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(Position, itemType, pitch, clipIndex); - /// - /// Place a blood decal for this player using a raycast from the player's position. - /// - /// The direction in which the raycast is fired to detect a surface. - public void PlaceBlood(Vector3 direction) - { - if (Physics.Raycast(Position, direction, out RaycastHit hitInfo, ImpactEffectsModule.ReceivingLayers)) - SpawnBlood(hitInfo.point + (hitInfo.normal * Decal.SurfaceDistance), -hitInfo.normal); - } + /// + public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); /// /// Spawns a blood decal for this player. From b74a19e766915fe59072b350894b69581d295bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sun, 7 Jun 2026 16:57:44 +0300 Subject: [PATCH 12/28] removing --- .../Exiled.API/Extensions/MirrorExtensions.cs | 70 +++++++++++++++++++ EXILED/Exiled.API/Features/Player.cs | 49 ------------- 2 files changed, 70 insertions(+), 49 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 7bf75549bd..9a4afb78a5 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -16,24 +16,36 @@ namespace Exiled.API.Extensions using System.Text; using AdminToys; + using AudioPooling; + using Cassie; + using CustomPlayerEffects; + + using Decals; + using Exiled.API.Enums; using Exiled.API.Features.Items; using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups.Keycards; + using Features; using Features.Pools; + using HarmonyLib; + using InventorySystem; using InventorySystem.Items; using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Keycards; + using MEC; + using Mirror; + using PlayerRoles; using PlayerRoles.Blood; using PlayerRoles.FirstPersonControl; @@ -41,12 +53,19 @@ namespace Exiled.API.Extensions using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.Spectating; using PlayerRoles.Voice; + using RelativePositioning; + using Respawning; + using Unity.Collections.LowLevel.Unsafe; + using UnityEngine; + using Utils.Networking; + using Firearm = Features.Items.Firearm; + /// /// A set of extensions for Networking. /// @@ -257,6 +276,57 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp }); } + /// + /// Spawns a blood decal for this player. + /// + /// Target to spawn blood decal for. + /// The position of the blood decal. + /// The raycast origin used to determine the decal's orientation. + /// if the blood decal was successfully spawned; otherwise, . + public static bool SpawnBlood(this Player player, Vector3 position, Vector3 sourcePosition) => SpawnDecal(player, position, sourcePosition, DecalPoolType.Blood); + + /// + /// Spawns a decal for this player. + /// + /// Target to spawn decal for. + /// The position of the decal. + /// The raycast origin used to determine the decal's orientation. + /// The . + /// The to use. + /// if the decal was successfully spawned; otherwise, . + public static bool SpawnDecal(this Player player, Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) + { + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + Firearm firearm = Item.Get(itemBase); + if (firearm == null) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; + if (impactEffectsModule == null) + { + Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); + return false; + } + + impactEffectsModule.SendRpc(player.ReferenceHub, writer => + { + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)decalType); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(sourcePosition)); + }); + + return true; + } + /// /// Place blood that only the can see. /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index ce5a388c20..530e4f8093 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3867,55 +3867,6 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = /// public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); - /// - /// Spawns a blood decal for this player. - /// - /// The position of the blood decal. - /// The raycast origin used to determine the decal's orientation. - /// if the blood decal was successfully spawned; otherwise, . - public bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood); - - /// - /// Spawns a decal for this player. - /// - /// The position of the decal. - /// The raycast origin used to determine the decal's orientation. - /// The . - /// The to use. - /// if the decal was successfully spawned; otherwise, . - public bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) - { - if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) - { - Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); - return false; - } - - Firearm firearm = Item.Get(itemBase); - if (firearm == null) - { - Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); - return false; - } - - ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; - if (impactEffectsModule == null) - { - Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); - return false; - } - - impactEffectsModule.SendRpc(ReferenceHub, writer => - { - writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); - writer.WriteByte((byte)decalType); - writer.WriteRelativePosition(new RelativePosition(position)); - writer.WriteRelativePosition(new RelativePosition(sourcePosition)); - }); - - return true; - } - /// public IEnumerable GetNearCameras(float toleration = 15f) => Map.GetNearCameras(Position, toleration); From 191f17f5cc753b7e2a3bb96fc841f00a60b6a292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sun, 7 Jun 2026 17:02:54 +0300 Subject: [PATCH 13/28] movevininggg --- .../Exiled.API/Extensions/MirrorExtensions.cs | 53 +++++++++++++++++++ EXILED/Exiled.API/Features/Map.cs | 53 ------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 9a4afb78a5..d28234d4a4 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -285,6 +285,15 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp /// if the blood decal was successfully spawned; otherwise, . public static bool SpawnBlood(this Player player, Vector3 position, Vector3 sourcePosition) => SpawnDecal(player, position, sourcePosition, DecalPoolType.Blood); + /// + /// Spawns a blood decal for the specified players. + /// + /// The players for which to spawn the blood decal. + /// The position of the blood decal. + /// The raycast origin used to determine the decal's orientation. + /// if the blood decal was successfully spawned; otherwise, . + public static bool SpawnBlood(this IEnumerable players, Vector3 position, Vector3 sourcePosition) => SpawnDecal(players, position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); + /// /// Spawns a decal for this player. /// @@ -327,6 +336,50 @@ public static bool SpawnDecal(this Player player, Vector3 position, Vector3 sour return true; } + /// + /// Spawns a decal for the specified targets. + /// + /// The targets for which to spawn the decal. + /// The position of the decal. + /// The raycast origin used to determine the decal's orientation. + /// The . + /// The to use. + /// if the decal was successfully spawned; otherwise, . + public static bool SpawnDecal(this IEnumerable targets, Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) + { + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + Firearm firearm = Item.Get(itemBase); + if (firearm == null) + { + Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); + return false; + } + + ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; + if (impactEffectsModule == null) + { + Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); + return false; + } + + HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); + + impactEffectsModule.SendRpc(targetHubs.Contains, writer => + { + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)decalType); + writer.WriteRelativePosition(new RelativePosition(position)); + writer.WriteRelativePosition(new RelativePosition(sourcePosition)); + }); + + return true; + } + /// /// Place blood that only the can see. /// diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index a29fb1d21f..748743ef2a 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -465,15 +465,6 @@ public static void PlaceBlood(Vector3 position, Vector3 direction) /// if the blood decal was successfully spawned; otherwise, . public static bool SpawnBlood(Vector3 position, Vector3 sourcePosition) => SpawnDecal(position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); - /// - /// Spawns a blood decal for the specified players. - /// - /// The players for which to spawn the blood decal. - /// The position of the blood decal. - /// The raycast origin used to determine the decal's orientation. - /// if the blood decal was successfully spawned; otherwise, . - public static bool SpawnBlood(IEnumerable players, Vector3 position, Vector3 sourcePosition) => SpawnDecal(players, position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15); - /// /// Spawns a decal. /// @@ -515,50 +506,6 @@ public static bool SpawnDecal(Vector3 position, Vector3 sourcePosition, DecalPoo return true; } - /// - /// Spawns a decal for the specified targets. - /// - /// The targets for which to spawn the decal. - /// The position of the decal. - /// The raycast origin used to determine the decal's orientation. - /// The . - /// The to use. - /// if the decal was successfully spawned; otherwise, . - public static bool SpawnDecal(IEnumerable targets, Vector3 position, Vector3 sourcePosition, DecalPoolType decalType, FirearmType firearmType = FirearmType.Com15) - { - if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) - { - Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); - return false; - } - - Firearm firearm = Item.Get(itemBase); - if (firearm == null) - { - Log.Error($"Failed to spawn decal: Could not find a Firearm for {firearmType}."); - return false; - } - - ImpactEffectsModule impactEffectsModule = firearm.ImpactEffectsModule; - if (impactEffectsModule == null) - { - Log.Error($"Failed to spawn decal: Could not find an ImpactEffectsModule for {firearmType}."); - return false; - } - - HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - - impactEffectsModule.SendRpc(targetHubs.Contains, writer => - { - writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); - writer.WriteByte((byte)decalType); - writer.WriteRelativePosition(new RelativePosition(position)); - writer.WriteRelativePosition(new RelativePosition(sourcePosition)); - }); - - return true; - } - /// /// Gets all the near cameras. /// From eb6bfbf523ec401583a07523a22e339f278fb41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 00:30:15 +0300 Subject: [PATCH 14/28] Indicator method --- EXILED/Exiled.API/Features/Player.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 530e4f8093..714f6a2bd3 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -52,6 +52,7 @@ namespace Exiled.API.Features using InventorySystem.Items.Armor; using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms.Attachments; + using InventorySystem.Items.Firearms.BasicMessages; using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Firearms.ShotEvents; using InventorySystem.Items.Usables; @@ -94,6 +95,7 @@ namespace Exiled.API.Features using static DamageHandlers.DamageHandlerBase; using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; + using static UnityEngine.GraphicsBuffer; using DamageHandlerBase = PlayerStatsSystem.DamageHandlerBase; using Firearm = Items.Firearm; @@ -3867,6 +3869,21 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = /// public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); + /// + /// Sends a damage indicator to player. + /// + /// The amount of damage dealt. Controls the size of the indicator. + /// The world position the damage originated from. + /// If true, spectators watching this player will also see the indicator. + public void SendDamageIndicator(float dmgDealt, Vector3 position, bool sendSpectatorsToo = true) + { + DamageIndicatorMessage damageIndicatorMessage = new(dmgDealt, position); + if (sendSpectatorsToo) + Connection.Send(damageIndicatorMessage); + else + damageIndicatorMessage.SendToSpectatorsOf(ReferenceHub, true); + } + /// public IEnumerable GetNearCameras(float toleration = 15f) => Map.GetNearCameras(Position, toleration); From 92cf98c4f9c495f93a0dafccd38af42e43ce65d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 00:32:48 +0300 Subject: [PATCH 15/28] Where did this come from? --- EXILED/Exiled.API/Features/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 714f6a2bd3..f245045823 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -95,7 +95,6 @@ namespace Exiled.API.Features using static DamageHandlers.DamageHandlerBase; using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; - using static UnityEngine.GraphicsBuffer; using DamageHandlerBase = PlayerStatsSystem.DamageHandlerBase; using Firearm = Items.Firearm; From ec87764d8c86c81980606455bec0490683494841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 02:25:37 +0300 Subject: [PATCH 16/28] sound & fake shoot --- .../Exiled.API/Extensions/MirrorExtensions.cs | 134 ++++++++++++++++++ EXILED/Exiled.API/Features/Items/Firearm.cs | 91 ++++++++++++ 2 files changed, 225 insertions(+) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index d28234d4a4..50b4ea8d39 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -29,6 +29,7 @@ namespace Exiled.API.Extensions using Exiled.API.Features.Items; using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups.Keycards; + using Exiled.API.Interfaces; using Features; using Features.Pools; @@ -64,6 +65,8 @@ namespace Exiled.API.Extensions using Utils.Networking; + using static InventorySystem.Items.Firearms.Modules.AutomaticActionModule; + using Firearm = Features.Items.Firearm; /// @@ -276,6 +279,137 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp }); } + /// + /// Plays a gun sound to the specified player. + /// + /// The firearm whose to use. + /// The index of the audio clip to play. + /// The to play the sound on. + /// The range of the sound. + /// The pitch of the sound. + /// The world position the sound originates from. + /// Whether the shooter is visible to the target. If , the sound will be played at instead of on the firearm's transform. + /// The player to send the sound to. + /// if the sound was played successfully; if is . + public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, Player target) + { + if (firearm.AudioModule == null) + { + Log.Error($"Firearm {firearm} doesn't have an audio module."); + return false; + } + + if (target == null) + { + Log.Error("Target player is null."); + return false; + } + + firearm.AudioModule.SendRpc(target.ReferenceHub, writer => + firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + + return true; + } + + /// + /// Plays a gun sound to the specified players. + /// + /// The firearm whose to use. + /// The index of the audio clip to play. + /// The to play the sound on. + /// The range of the sound. + /// The pitch of the sound. + /// The world position the sound originates from. + /// Whether the shooter is visible to the target. If , the sound will be played at instead of on the firearm's transform. + /// The players to send the sound to. + /// if the sound was played successfully; if is . + public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, IEnumerable targets) + { + if (firearm.AudioModule == null) + { + Log.Error($"Firearm {firearm} doesn't have an audio module."); + return false; + } + + if (targets == null) + { + Log.Error("Failed to play sound, targets is null."); + return false; + } + + HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); + + firearm.AudioModule.SendRpc(targetHubs.Contains, writer => + firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + + return true; + } + + /// + /// Sends a RPC to the specified players. + /// + /// The firearm whose to use for sending the RPC. + /// The type of RPC to send. + /// The player to send the RPC to. + /// The number of chambers fired. Only used when is . + /// if the RPC was sent successfully; if is . + public static bool SendRpc(this Firearm firearm, MessageHeader header, Player target, byte chambersFired = 1) + { + if (firearm.AutomaticActionModule == null) + { + Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); + return false; + } + + if (target?.GameObject == null) + { + Log.Error("Failed to send RPC, target player is null."); + return false; + } + + firearm.AutomaticActionModule.SendRpc(target.ReferenceHub, writer => + { + writer.WriteSubheader(header); + if (header == MessageHeader.RpcFire) + writer.WriteByte(chambersFired); + }); + + return true; + } + + /// + /// Sends a RPC to the specified players. + /// + /// The firearm whose to use for sending the RPC. + /// The type of RPC to send. + /// The players to send the RPC to. + /// The number of chambers fired. Only used when is . + /// if the RPC was sent successfully; if is . + public static bool SendRpc(this Firearm firearm, MessageHeader header, IEnumerable targets, byte chambersFired = 1) + { + if (firearm.AutomaticActionModule == null) + { + Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); + return false; + } + + if (targets == null) + { + Log.Error("Failed to send RPC, targets is null."); + return false; + } + + HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); + firearm.AutomaticActionModule.SendRpc(targetHubs.Contains, writer => + { + writer.WriteSubheader(header); + if (header == MessageHeader.RpcFire) + writer.WriteByte(chambersFired); + }); + + return true; + } + /// /// Spawns a blood decal for this player. /// diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index ac2d934f4a..4591ad3571 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -11,6 +11,8 @@ namespace Exiled.API.Features.Items using System.Collections.Generic; using System.Linq; + using AudioPooling; + using CameraShaking; using Enums; @@ -27,13 +29,18 @@ namespace Exiled.API.Features.Items using InventorySystem; using InventorySystem.Items; using InventorySystem.Items.Autosync; + using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; + using Mirror; + using UnityEngine; using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; + using static InventorySystem.Items.Firearms.Modules.AutomaticActionModule; + using static UnityEngine.GraphicsBuffer; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; using FirearmPickup = Pickups.FirearmPickup; @@ -72,6 +79,8 @@ public Firearm(BaseFirearm itemBase) case IAmmoContainerModule ammoModule: BarrelMagazine ??= (BarrelMagazine)Magazine.Get(ammoModule); + if (module is AutomaticActionModule automaticActionModule) + AutomaticActionModule = automaticActionModule; break; case HitscanHitregModuleBase hitregModule: @@ -86,6 +95,10 @@ public Firearm(BaseFirearm itemBase) ImpactEffectsModule = impactEffectsModule; break; + case AudioModule audioModule: + AudioModule = audioModule; + break; + default: break; } @@ -167,6 +180,16 @@ public static IReadOnlyDictionary public ImpactEffectsModule ImpactEffectsModule { get; } + /// + /// Gets an automatic action module for the current firearm. + /// + public AutomaticActionModule AutomaticActionModule { get; } + + /// + /// Gets an audio module for the current firearm. + /// + public AudioModule AudioModule { get; } + /// /// Gets or sets the amount of ammo in the firearm magazine. /// @@ -782,6 +805,74 @@ public void Unload() AnimatorReloaderModule.SendRpcHeaderWithRandomByte(ReloaderMessageHeader.Unload); } + /// + /// Plays a firearm sound to nearby players. + /// + /// The index of the audio clip to play. + /// The to play the sound on. + /// The range within which nearby players can hear the sound. + /// The pitch of the sound. + /// if the sound was played successfully; if is . + public bool PlaySound(int index, MixerChannel channel, float range, float pitch) + { + if (AudioModule == null) + { + Log.Error($"Failed to play sound, firearm {Type} does not have an AudioModule."); + return false; + } + + AudioModule.ServerSendToNearbyPlayers(index, channel, range, pitch); + return true; + } + + /// + /// Simulates a fire. + /// + /// The number of chambers fired. + /// if both the sound,RPC and impact effects were sent successfully; if either or is . + public bool FakeFire(byte chambersFired = 1) + { + // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. + bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); + bool visualFlag = SendRpc(MessageHeader.RpcFire, chambersFired); + bool impactEffects = ImpactEffectsModule != null; + if (impactEffects) + { + Transform camera = Owner.CameraTransform; + float maxDist = HitscanHitregModule.FullDamageDistance + HitscanHitregModule.DamageFalloffDistance; + + if (Physics.Raycast(camera.position, camera.forward, out RaycastHit hit, maxDist, HitscanHitregModuleBase.HitregMask)) + ImpactEffectsModule.ServerProcessHit(hit, camera.position, true); + } + + return soundFlag && visualFlag && impactEffects; + } + + /// + /// Sends a RPC to the specified players. + /// + /// The type of RPC to send. + /// The number of chambers fired. Only used when is . + /// if the RPC was sent successfully; if is . + public bool SendRpc(MessageHeader header, byte chambersFired = 1) + { + if (AutomaticActionModule == null) + { + Log.Error($"Failed to send RPC, firearm {Type} does not have an AutomaticActionModule."); + return false; + } + + AutomaticActionModule.SendRpc( + writer => + { + writer.WriteSubheader(header); + if (header == MessageHeader.RpcFire) + writer.WriteByte(chambersFired); + }, true); + + return true; + } + /// /// Clones current object. /// From 12d5e24ce7e39b5c023f0bdd02e5fdfeeb4abc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 17:00:58 +0300 Subject: [PATCH 17/28] update --- .../Exiled.API/Extensions/MirrorExtensions.cs | 98 ++++++++++++------- EXILED/Exiled.API/Features/Items/Firearm.cs | 35 +------ EXILED/Exiled.API/Features/Player.cs | 21 ++-- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 50b4ea8d39..04c3d6a16b 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -227,30 +227,42 @@ public static ReadOnlyDictionary RpcFullNames /// /// Target to play. /// Position to play on. - /// Weapon' sound to play. - /// Sound's volume to set. - /// GunAudioMessage's audioClipId to set (default = 0). - [Obsolete("This method is not working. Use PlayGunSound(Player, Vector3, FirearmType, float, int, bool) overload instead.")] - public static void PlayGunSound(this Player player, Vector3 position, ItemType itemType, byte volume, byte audioClipId = 0) - => PlayGunSound(player, position, itemType.GetFirearmType(), volume, audioClipId); + /// Weapon's sound to play. + /// Speed of sound. + /// Index of clip. + [Obsolete("This method is deprecated, use PlayGunSound(this Player, FirearmType, int, Vector3, MixerChannel, float, float) instead.")] + public static void PlayGunSound(this Player player, Vector3 position, FirearmType firearmType, float pitch = 1, int clipIndex = 0) => player.PlayGunSound(firearmType, clipIndex, position, pitch: pitch); /// /// Plays a gun sound that only the can hear. /// /// Target to play. - /// Position to play on. /// Weapon's sound to play. - /// Speed of sound. /// Index of clip. - public static void PlayGunSound(this Player player, Vector3 position, FirearmType firearmType, float pitch = 1, int clipIndex = 0) + /// Position to play on. + /// Audio's mixer channel. + /// Max range of sound. + /// Speed of sound. + public static void PlayGunSound(this Player player, FirearmType firearmType, int clipIndex, Vector3 position, MixerChannel mixerChannel = MixerChannel.Weapons, float range = 12f, float pitch = 1) { - if (firearmType is FirearmType.ParticleDisruptor or FirearmType.None) + if (firearmType is FirearmType.None) + { + Log.Error($"Failed to play gun sound for player {player.Nickname} because firearm type was None."); return; + } - Features.Items.Firearm firearm = Features.Items.Firearm.ItemTypeToFirearmInstance[firearmType]; + if (!InventoryItemLoader.TryGetItem(firearmType.GetItemType(), out ItemBase itemBase)) + { + Log.Error($"Failed to get ItemBase for firearm type {firearmType} when trying to play gun sound for player {player.Nickname}"); + return; + } + Firearm firearm = Item.Get(itemBase); if (firearm == null) + { + Log.Error($"Failed to get Firearm for firearm type {firearmType} when trying to play gun sound."); return; + } using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { @@ -261,20 +273,15 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp player.Connection.Send(writer); } - firearm.BarrelAmmo = 1; - firearm.BarrelMagazine.IsCocked = true; player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), firearm.Identifier); - if (!firearm.Base.TryGetModule(out AudioModule audioModule)) - return; - - Timing.CallDelayed(0.1f, () => // due to selecting item we need to delay shot a bit + Timing.CallDelayed(0.1f, () => { - audioModule.SendRpc(player.ReferenceHub, writer => - audioModule.ServerSend(writer, clipIndex, pitch, MixerChannel.Weapons, 12f, position, false)); + if (!player.IsConnected) + return; + firearm.PlaySound(clipIndex, mixerChannel, range, pitch, position, false, player); player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), ItemIdentifier.None); - player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub, null)); }); } @@ -305,9 +312,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann return false; } - firearm.AudioModule.SendRpc(target.ReferenceHub, writer => - firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); - + firearm.AudioModule.SendRpc(target.ReferenceHub, writer => firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); return true; } @@ -339,8 +344,33 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - firearm.AudioModule.SendRpc(targetHubs.Contains, writer => - firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + firearm.AudioModule.SendRpc(targetHubs.Contains, writer => firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + return true; + } + + /// + /// Sends a RPC to the specified players. + /// + /// The to send the RPC from. + /// The type of RPC to send. + /// The number of chambers fired. Only used when is . + /// if the RPC was sent successfully; if is . + public static bool SendRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) + { + AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; + if (automaticActionModule == null) + { + Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); + return false; + } + + automaticActionModule.SendRpc( + writer => + { + writer.WriteSubheader(header); + if (header == MessageHeader.RpcFire) + writer.WriteByte(chambersFired); + }, true); return true; } @@ -349,13 +379,14 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// Sends a RPC to the specified players. /// /// The firearm whose to use for sending the RPC. - /// The type of RPC to send. /// The player to send the RPC to. + /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendRpc(this Firearm firearm, MessageHeader header, Player target, byte chambersFired = 1) + public static bool SendRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) { - if (firearm.AutomaticActionModule == null) + AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; + if (automaticActionModule == null) { Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); return false; @@ -367,7 +398,7 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, Player ta return false; } - firearm.AutomaticActionModule.SendRpc(target.ReferenceHub, writer => + automaticActionModule.SendRpc(target.ReferenceHub, writer => { writer.WriteSubheader(header); if (header == MessageHeader.RpcFire) @@ -381,13 +412,14 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, Player ta /// Sends a RPC to the specified players. /// /// The firearm whose to use for sending the RPC. - /// The type of RPC to send. /// The players to send the RPC to. + /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendRpc(this Firearm firearm, MessageHeader header, IEnumerable targets, byte chambersFired = 1) + public static bool SendRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) { - if (firearm.AutomaticActionModule == null) + AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; + if (automaticActionModule == null) { Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); return false; @@ -400,7 +432,7 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, IEnumerab } HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - firearm.AutomaticActionModule.SendRpc(targetHubs.Contains, writer => + automaticActionModule.SendRpc(targetHubs.Contains, writer => { writer.WriteSubheader(header); if (header == MessageHeader.RpcFire) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 4591ad3571..c31b9ad563 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -829,14 +829,14 @@ public bool PlaySound(int index, MixerChannel channel, float range, float pitch) /// Simulates a fire. /// /// The number of chambers fired. - /// if both the sound,RPC and impact effects were sent successfully; if either or is . + /// if the sound, RPC, and impact effects were all submitted successfully; if either or is . public bool FakeFire(byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); - bool visualFlag = SendRpc(MessageHeader.RpcFire, chambersFired); - bool impactEffects = ImpactEffectsModule != null; - if (impactEffects) + bool visualFlag = this.SendRpc(MessageHeader.RpcFire, chambersFired); + bool impactEffectsFlag = ImpactEffectsModule != null; + if (impactEffectsFlag) { Transform camera = Owner.CameraTransform; float maxDist = HitscanHitregModule.FullDamageDistance + HitscanHitregModule.DamageFalloffDistance; @@ -845,32 +845,7 @@ public bool FakeFire(byte chambersFired = 1) ImpactEffectsModule.ServerProcessHit(hit, camera.position, true); } - return soundFlag && visualFlag && impactEffects; - } - - /// - /// Sends a RPC to the specified players. - /// - /// The type of RPC to send. - /// The number of chambers fired. Only used when is . - /// if the RPC was sent successfully; if is . - public bool SendRpc(MessageHeader header, byte chambersFired = 1) - { - if (AutomaticActionModule == null) - { - Log.Error($"Failed to send RPC, firearm {Type} does not have an AutomaticActionModule."); - return false; - } - - AutomaticActionModule.SendRpc( - writer => - { - writer.WriteSubheader(header); - if (header == MessageHeader.RpcFire) - writer.WriteByte(chambersFired); - }, true); - - return true; + return soundFlag && visualFlag && impactEffectsFlag; } /// diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index f245045823..c08643bd9b 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -14,6 +14,8 @@ namespace Exiled.API.Features using System.Reflection; using System.Runtime.CompilerServices; + using AudioPooling; + using Core; using CustomPlayerEffects; @@ -21,8 +23,6 @@ namespace Exiled.API.Features using DamageHandlers; - using Decals; - using Enums; using Exiled.API.Features.Core.Interfaces; @@ -3856,14 +3856,8 @@ public void Reconnect(ushort newPort = 0, float delay = 5, bool reconnect = true Connection.Send(new RoundRestartMessage(roundRestartType, delay, newPort, reconnect, false)); } - /// - [Obsolete("Use PlayGunSound(Player, Vector3, FirearmType, byte, byte) instead.")] - public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) - => PlayGunSound(type.GetFirearmType(), volume, audioClipId); - - /// - public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => - this.PlayGunSound(Position, itemType, pitch, clipIndex); + /// + public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(itemType, clipIndex, Position, pitch: pitch); /// public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction); @@ -3871,10 +3865,10 @@ public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = /// /// Sends a damage indicator to player. /// - /// The amount of damage dealt. Controls the size of the indicator. + /// The amount of damage dealt. Controls the size of the damage indicator. /// The world position the damage originated from. /// If true, spectators watching this player will also see the indicator. - public void SendDamageIndicator(float dmgDealt, Vector3 position, bool sendSpectatorsToo = true) + public void SendHitEffect(float dmgDealt, Vector3 position, bool sendSpectatorsToo = true) { DamageIndicatorMessage damageIndicatorMessage = new(dmgDealt, position); if (sendSpectatorsToo) @@ -3896,8 +3890,7 @@ public void SendDamageIndicator(float dmgDealt, Vector3 position, bool sendSpect /// Teleports the player to the given object, with no offset. /// /// The object to teleport to. - public void Teleport(object obj) - => Teleport(obj, Vector3.zero); + public void Teleport(object obj) => Teleport(obj, Vector3.zero); /// /// Teleports the player to the given object, offset by the defined offset value. From cd07ff6d4e58da0030f2150814370c69b6b29125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 17:16:16 +0300 Subject: [PATCH 18/28] gix --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 8 +++----- EXILED/Exiled.API/Features/Items/Firearm.cs | 6 ------ EXILED/Exiled.API/Features/Player.cs | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index e78746755d..4001908308 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -25,12 +25,11 @@ namespace Exiled.API.Extensions using Decals; using Exiled.API.Enums; + using Exiled.API.Features.Items; using Exiled.API.Features.Items.Keycards; using Exiled.API.Features.Pickups.Keycards; - using Exiled.API.Interfaces; using Features; - using Features.Pools; using HarmonyLib; @@ -54,8 +53,6 @@ namespace Exiled.API.Extensions using RelativePositioning; - using Respawning; - using Unity.Collections.LowLevel.Unsafe; using UnityEngine; @@ -364,7 +361,8 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, byte cham writer.WriteSubheader(header); if (header == MessageHeader.RpcFire) writer.WriteByte(chambersFired); - }, true); + }, + true); return true; } diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index c3a48f64b9..667585abf4 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -25,21 +25,15 @@ namespace Exiled.API.Features.Items using Extensions; - using InventorySystem; - using InventorySystem.Items; using InventorySystem.Items.Autosync; - using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; - using Mirror; - using UnityEngine; using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using static InventorySystem.Items.Firearms.Modules.AutomaticActionModule; - using static UnityEngine.GraphicsBuffer; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; using FirearmPickup = Pickups.FirearmPickup; diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 21d99a5f83..75edf2a364 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -50,7 +50,6 @@ namespace Exiled.API.Features using InventorySystem.Disarming; using InventorySystem.Items; using InventorySystem.Items.Armor; - using InventorySystem.Items.Autosync; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.BasicMessages; using InventorySystem.Items.Firearms.Modules; From 2b1c5ae1c7d60f2bc7fed83251fe1984415a5249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 18:14:32 +0300 Subject: [PATCH 19/28] docs & wrong if check --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 4 ++-- EXILED/Exiled.API/Features/Player.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 4001908308..125d0405f8 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -340,7 +340,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann } /// - /// Sends a RPC to the specified players. + /// Sends a RPC. /// /// The to send the RPC from. /// The type of RPC to send. @@ -368,7 +368,7 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, byte cham } /// - /// Sends a RPC to the specified players. + /// Sends a RPC to the specified player. /// /// The firearm whose to use for sending the RPC. /// The player to send the RPC to. diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 75edf2a364..7afb0ddfad 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3873,7 +3873,7 @@ public void Reconnect(ushort newPort = 0, float delay = 5, bool reconnect = true public void SendHitEffect(float dmgDealt, Vector3 position, bool sendSpectatorsToo = true) { DamageIndicatorMessage damageIndicatorMessage = new(dmgDealt, position); - if (sendSpectatorsToo) + if (!sendSpectatorsToo) Connection.Send(damageIndicatorMessage); else damageIndicatorMessage.SendToSpectatorsOf(ReferenceHub, true); From 7e2bf468d28d419372a2d45d94d4eb616f3ad68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 21:44:12 +0300 Subject: [PATCH 20/28] cahced variables --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 125d0405f8..8aa0e84ffe 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -291,7 +291,8 @@ public static void PlayGunSound(this Player player, FirearmType firearmType, int /// if the sound was played successfully; if is . public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, Player target) { - if (firearm.AudioModule == null) + AudioModule audioModule = firearm.AudioModule; + if (audioModule == null) { Log.Error($"Firearm {firearm} doesn't have an audio module."); return false; @@ -303,7 +304,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann return false; } - firearm.AudioModule.SendRpc(target.ReferenceHub, writer => firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + audioModule.SendRpc(target.ReferenceHub, writer => audioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); return true; } @@ -321,7 +322,8 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// if the sound was played successfully; if is . public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, IEnumerable targets) { - if (firearm.AudioModule == null) + AudioModule audioModule = firearm.AudioModule; + if (audioModule == null) { Log.Error($"Firearm {firearm} doesn't have an audio module."); return false; @@ -335,7 +337,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - firearm.AudioModule.SendRpc(targetHubs.Contains, writer => firearm.AudioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + audioModule.SendRpc(targetHubs.Contains, writer => audioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); return true; } From f316fec2dc9fcd817b2d6c928e06b77cca8cdd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 22:00:40 +0300 Subject: [PATCH 21/28] renaming --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 6 +++--- EXILED/Exiled.API/Features/Items/Firearm.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 8aa0e84ffe..05107d1d85 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -348,7 +348,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) + public static bool SendActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -377,7 +377,7 @@ public static bool SendRpc(this Firearm firearm, MessageHeader header, byte cham /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) + public static bool SendActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -410,7 +410,7 @@ public static bool SendRpc(this Firearm firearm, Player target, MessageHeader he /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) + public static bool SendActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 667585abf4..48b997b3c7 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -816,7 +816,7 @@ public bool FakeFire(byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); - bool visualFlag = this.SendRpc(MessageHeader.RpcFire, chambersFired); + bool visualFlag = this.SendActionModuleRpc(MessageHeader.RpcFire, chambersFired); bool impactEffectsFlag = ImpactEffectsModule != null; if (impactEffectsFlag) { From b5187e848505946c4a50fcbd43d15eb77d29b6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Mon, 8 Jun 2026 23:56:19 +0300 Subject: [PATCH 22/28] dellete --- EXILED/Exiled.API/Enums/BloodType.cs | 40 ---------------------------- 1 file changed, 40 deletions(-) delete mode 100644 EXILED/Exiled.API/Enums/BloodType.cs diff --git a/EXILED/Exiled.API/Enums/BloodType.cs b/EXILED/Exiled.API/Enums/BloodType.cs deleted file mode 100644 index 369793e2ad..0000000000 --- a/EXILED/Exiled.API/Enums/BloodType.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) ExMod Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.API.Enums -{ - using System; - - /// - /// Unique identifier for the different types of blood decals. - /// - /// - /// - [Obsolete("This blood decal are outdated now used DecalPoolType.Blood", true)] - public enum BloodType - { - /// - /// The default blood decal. - /// - Default, - - /// - /// The blood decal placed after Scp106 sends someone to the pocket dimension. - /// - Scp106, - - /// - /// The spreaded blood decal. - /// - Spreaded, - - /// - /// The faded blood decal. - /// - Faded, - } -} \ No newline at end of file From e7342c2f5a6e76a5537e3ba992d064c2c7fcc75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 00:34:32 +0300 Subject: [PATCH 23/28] i realized there is double, pump ,disruptor action modulle too so i renamed again --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 6 +++--- EXILED/Exiled.API/Features/Items/Firearm.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 05107d1d85..feb910ef4b 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -348,7 +348,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) + public static bool SendAutoActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -377,7 +377,7 @@ public static bool SendActionModuleRpc(this Firearm firearm, MessageHeader heade /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) + public static bool SendAutoActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -410,7 +410,7 @@ public static bool SendActionModuleRpc(this Firearm firearm, Player target, Mess /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) + public static bool SendAutoActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 48b997b3c7..1a87c947b0 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -816,7 +816,7 @@ public bool FakeFire(byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); - bool visualFlag = this.SendActionModuleRpc(MessageHeader.RpcFire, chambersFired); + bool visualFlag = this.SendAutoActionModuleRpc(MessageHeader.RpcFire, chambersFired); bool impactEffectsFlag = ImpactEffectsModule != null; if (impactEffectsFlag) { From f591c6e81f72ab71c251c28e252706f042f6342f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 00:35:13 +0300 Subject: [PATCH 24/28] f --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index feb910ef4b..32f84e09cf 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -342,7 +342,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann } /// - /// Sends a RPC. + /// Sends a Automatic Action Module RPC. /// /// The to send the RPC from. /// The type of RPC to send. @@ -370,7 +370,7 @@ public static bool SendAutoActionModuleRpc(this Firearm firearm, MessageHeader h } /// - /// Sends a RPC to the specified player. + /// Sends a Automatic Action Module RPC to the specified player. /// /// The firearm whose to use for sending the RPC. /// The player to send the RPC to. @@ -403,7 +403,7 @@ public static bool SendAutoActionModuleRpc(this Firearm firearm, Player target, } /// - /// Sends a RPC to the specified players. + /// Sends a Automatic Action Module RPC to the specified players. /// /// The firearm whose to use for sending the RPC. /// The players to send the RPC to. From d3d142438b4f2e5aa1d8eec1dc1d672fb68a44c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 00:36:17 +0300 Subject: [PATCH 25/28] fuck it let be longht --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 6 +++--- EXILED/Exiled.API/Features/Items/Firearm.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 32f84e09cf..bc8edc3c91 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -348,7 +348,7 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendAutoActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) + public static bool SendAutomaticActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -377,7 +377,7 @@ public static bool SendAutoActionModuleRpc(this Firearm firearm, MessageHeader h /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendAutoActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) + public static bool SendAutomaticActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) @@ -410,7 +410,7 @@ public static bool SendAutoActionModuleRpc(this Firearm firearm, Player target, /// The type of RPC to send. /// The number of chambers fired. Only used when is . /// if the RPC was sent successfully; if is . - public static bool SendAutoActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) + public static bool SendAutomaticActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) { AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; if (automaticActionModule == null) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 1a87c947b0..7cf96269a8 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -816,7 +816,7 @@ public bool FakeFire(byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); - bool visualFlag = this.SendAutoActionModuleRpc(MessageHeader.RpcFire, chambersFired); + bool visualFlag = this.SendAutomaticActionModuleRpc(MessageHeader.RpcFire, chambersFired); bool impactEffectsFlag = ImpactEffectsModule != null; if (impactEffectsFlag) { From 5f791fef04753eebf80a26967ed64bb3a2f18b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 15:01:31 +0300 Subject: [PATCH 26/28] Valera was right its basic rpcs we dont need its --- .../Exiled.API/Extensions/MirrorExtensions.cs | 95 ------------------- EXILED/Exiled.API/Features/Items/Firearm.cs | 25 +++-- 2 files changed, 17 insertions(+), 103 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index bc8edc3c91..54009f7c6e 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -341,101 +341,6 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann return true; } - /// - /// Sends a Automatic Action Module RPC. - /// - /// The to send the RPC from. - /// The type of RPC to send. - /// The number of chambers fired. Only used when is . - /// if the RPC was sent successfully; if is . - public static bool SendAutomaticActionModuleRpc(this Firearm firearm, MessageHeader header, byte chambersFired = 1) - { - AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; - if (automaticActionModule == null) - { - Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); - return false; - } - - automaticActionModule.SendRpc( - writer => - { - writer.WriteSubheader(header); - if (header == MessageHeader.RpcFire) - writer.WriteByte(chambersFired); - }, - true); - - return true; - } - - /// - /// Sends a Automatic Action Module RPC to the specified player. - /// - /// The firearm whose to use for sending the RPC. - /// The player to send the RPC to. - /// The type of RPC to send. - /// The number of chambers fired. Only used when is . - /// if the RPC was sent successfully; if is . - public static bool SendAutomaticActionModuleRpc(this Firearm firearm, Player target, MessageHeader header, byte chambersFired = 1) - { - AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; - if (automaticActionModule == null) - { - Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); - return false; - } - - if (target?.GameObject == null) - { - Log.Error("Failed to send RPC, target player is null."); - return false; - } - - automaticActionModule.SendRpc(target.ReferenceHub, writer => - { - writer.WriteSubheader(header); - if (header == MessageHeader.RpcFire) - writer.WriteByte(chambersFired); - }); - - return true; - } - - /// - /// Sends a Automatic Action Module RPC to the specified players. - /// - /// The firearm whose to use for sending the RPC. - /// The players to send the RPC to. - /// The type of RPC to send. - /// The number of chambers fired. Only used when is . - /// if the RPC was sent successfully; if is . - public static bool SendAutomaticActionModuleRpc(this Firearm firearm, IEnumerable targets, MessageHeader header, byte chambersFired = 1) - { - AutomaticActionModule automaticActionModule = firearm.AutomaticActionModule; - if (automaticActionModule == null) - { - Log.Error($"Failed to send RPC, firearm {firearm.Type} does not have an AutomaticActionModule."); - return false; - } - - if (targets == null) - { - Log.Error("Failed to send RPC, targets is null."); - return false; - } - - HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - automaticActionModule.SendRpc(targetHubs.Contains, writer => - { - writer.WriteSubheader(header); - if (header == MessageHeader.RpcFire) - writer.WriteByte(chambersFired); - }); - - return true; - } - /// /// Spawns a blood decal for this player. /// diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 7cf96269a8..5974f36372 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -810,15 +810,26 @@ public bool PlaySound(int index, MixerChannel channel, float range, float pitch) /// /// Simulates a fire. /// + /// Rpc header for fire type like fire or dry fire. /// The number of chambers fired. - /// if the sound, RPC, and impact effects were all submitted successfully; if either or is . - public bool FakeFire(byte chambersFired = 1) + public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. - bool soundFlag = PlaySound(1, MixerChannel.Weapons, 12f, 1f); - bool visualFlag = this.SendAutomaticActionModuleRpc(MessageHeader.RpcFire, chambersFired); - bool impactEffectsFlag = ImpactEffectsModule != null; - if (impactEffectsFlag) + PlaySound(1, MixerChannel.Weapons, 12f, 1f); + + if (AutomaticActionModule != null) + { + AutomaticActionModule.SendRpc( + writer => + { + writer.WriteSubheader(rpcHeader); + if (rpcHeader == MessageHeader.RpcFire) + writer.WriteByte(chambersFired); + }, + true); + } + + if (ImpactEffectsModule != null) { Transform camera = Owner.CameraTransform; float maxDist = HitscanHitregModule.FullDamageDistance + HitscanHitregModule.DamageFalloffDistance; @@ -826,8 +837,6 @@ public bool FakeFire(byte chambersFired = 1) if (Physics.Raycast(camera.position, camera.forward, out RaycastHit hit, maxDist, HitscanHitregModuleBase.HitregMask)) ImpactEffectsModule.ServerProcessHit(hit, camera.position, true); } - - return soundFlag && visualFlag && impactEffectsFlag; } /// From 1e4d70d5a1b3254a1464c16ad0f82cc93d24b3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 15:31:32 +0300 Subject: [PATCH 27/28] fix --- .../Exiled.API/Extensions/MirrorExtensions.cs | 40 ++++++++++++------- EXILED/Exiled.API/Features/Items/Firearm.cs | 14 +++++-- EXILED/Exiled.API/Features/Player.cs | 3 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 54009f7c6e..66452d24eb 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -59,8 +59,6 @@ namespace Exiled.API.Extensions using Utils.Networking; - using static InventorySystem.Items.Firearms.Modules.AutomaticActionModule; - using Firearm = Features.Items.Firearm; /// @@ -234,7 +232,7 @@ public static ReadOnlyDictionary RpcFullNames /// Audio's mixer channel. /// Max range of sound. /// Speed of sound. - public static void PlayGunSound(this Player player, FirearmType firearmType, int clipIndex, Vector3 position, MixerChannel mixerChannel = MixerChannel.Weapons, float range = 12f, float pitch = 1) + public static void PlayGunSound(this Player player, FirearmType firearmType, int clipIndex, Vector3 position, MixerChannel mixerChannel = MixerChannel.Weapons, float? range = null, float? pitch = null) { if (firearmType is FirearmType.None) { @@ -258,7 +256,7 @@ public static void PlayGunSound(this Player player, FirearmType firearmType, int using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { writer.WriteUShort(NetworkMessageId.Id); - new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.ClassD, player.ReferenceHub, null).Write(writer); + new RoleSyncInfo(Server.Host.ReferenceHub, RoleTypeId.Tutorial, player.ReferenceHub, null).Write(writer); writer.WriteRelativePosition(new RelativePosition(0, 0, 0, 0, false)); writer.WriteUShort(0); player.Connection.Send(writer); @@ -271,7 +269,7 @@ public static void PlayGunSound(this Player player, FirearmType firearmType, int if (!player.IsConnected) return; - firearm.PlaySound(clipIndex, mixerChannel, range, pitch, position, false, player); + firearm.PlaySound(player, clipIndex, mixerChannel, position, shooterVisible: false, range, pitch); player.SendFakeSyncVar(Server.Host.Inventory.netIdentity, typeof(Inventory), nameof(Inventory.NetworkCurItem), ItemIdentifier.None); player.Connection.Send(new RoleSyncInfo(Server.Host.ReferenceHub, Server.Host.Role, player.ReferenceHub, null)); }); @@ -281,15 +279,15 @@ public static void PlayGunSound(this Player player, FirearmType firearmType, int /// Plays a gun sound to the specified player. /// /// The firearm whose to use. + /// The player to send the sound to. /// The index of the audio clip to play. /// The to play the sound on. - /// The range of the sound. - /// The pitch of the sound. /// The world position the sound originates from. /// Whether the shooter is visible to the target. If , the sound will be played at instead of on the firearm's transform. - /// The player to send the sound to. + /// The range of the sound. + /// The pitch of the sound. /// if the sound was played successfully; if is . - public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, Player target) + public static bool PlaySound(this Firearm firearm, Player target, int index, MixerChannel channel, Vector3 position, bool shooterVisible, float? range = null, float? pitch = null) { AudioModule audioModule = firearm.AudioModule; if (audioModule == null) @@ -304,7 +302,13 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann return false; } - audioModule.SendRpc(target.ReferenceHub, writer => audioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + if (!range.HasValue) + range = audioModule.FinalGunshotRange; + + if (!pitch.HasValue) + pitch = audioModule.RandomPitch; + + audioModule.SendRpc(target.ReferenceHub, writer => audioModule.ServerSend(writer, index, pitch.Value, channel, range.Value, position, shooterVisible)); return true; } @@ -312,15 +316,15 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann /// Plays a gun sound to the specified players. /// /// The firearm whose to use. + /// The players to send the sound to. /// The index of the audio clip to play. /// The to play the sound on. - /// The range of the sound. - /// The pitch of the sound. /// The world position the sound originates from. /// Whether the shooter is visible to the target. If , the sound will be played at instead of on the firearm's transform. - /// The players to send the sound to. + /// The range of the sound. + /// The pitch of the sound. /// if the sound was played successfully; if is . - public static bool PlaySound(this Firearm firearm, int index, MixerChannel channel, float range, float pitch, Vector3 position, bool shooterVisible, IEnumerable targets) + public static bool PlaySound(this Firearm firearm, IEnumerable targets, int index, MixerChannel channel, Vector3 position, bool shooterVisible, float? range = null, float? pitch = null) { AudioModule audioModule = firearm.AudioModule; if (audioModule == null) @@ -335,9 +339,15 @@ public static bool PlaySound(this Firearm firearm, int index, MixerChannel chann return false; } + if (!range.HasValue) + range = audioModule.FinalGunshotRange; + + if (!pitch.HasValue) + pitch = audioModule.RandomPitch; + HashSet targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet(); - audioModule.SendRpc(targetHubs.Contains, writer => audioModule.ServerSend(writer, index, pitch, channel, range, position, shooterVisible)); + audioModule.SendRpc(targetHubs.Contains, writer => audioModule.ServerSend(writer, index, pitch.Value, channel, range.Value, position, shooterVisible)); return true; } diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 5974f36372..77d552c9e4 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -795,7 +795,7 @@ public void Unload() /// The range within which nearby players can hear the sound. /// The pitch of the sound. /// if the sound was played successfully; if is . - public bool PlaySound(int index, MixerChannel channel, float range, float pitch) + public bool PlaySound(int index, MixerChannel channel, float? range = null, float? pitch = null) { if (AudioModule == null) { @@ -803,7 +803,13 @@ public bool PlaySound(int index, MixerChannel channel, float range, float pitch) return false; } - AudioModule.ServerSendToNearbyPlayers(index, channel, range, pitch); + if (!range.HasValue) + range = AudioModule.FinalGunshotRange; + + if (!pitch.HasValue) + pitch = AudioModule.RandomPitch; + + AudioModule.ServerSendToNearbyPlayers(index, channel, range.Value, pitch.Value); return true; } @@ -815,8 +821,9 @@ public bool PlaySound(int index, MixerChannel channel, float range, float pitch) public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chambersFired = 1) { // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. - PlaySound(1, MixerChannel.Weapons, 12f, 1f); + PlaySound(1, MixerChannel.Weapons, AudioModule.FinalGunshotRange, AudioModule.RandomPitch); +#pragma warning disable IDE0031 // Use null propagation if (AutomaticActionModule != null) { AutomaticActionModule.SendRpc( @@ -828,6 +835,7 @@ public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chamb }, true); } +#pragma warning restore IDE0031 // Use null propagation if (ImpactEffectsModule != null) { diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 7afb0ddfad..ef43a01ebd 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3858,7 +3858,8 @@ public void Reconnect(ushort newPort = 0, float delay = 5, bool reconnect = true Connection.Send(new RoundRestartMessage(roundRestartType, delay, newPort, reconnect, false)); } - /// + /// + [Obsolete("Use Player::PlayGunSound(FirearmType, int, Vector3, MixerChannel, float?, float?) instead of this.")] public void PlayGunSound(FirearmType itemType, float pitch = 1, int clipIndex = 0) => this.PlayGunSound(itemType, clipIndex, Position, pitch: pitch); /// From 894b4af15a917cde0f624b941da212186b473092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Tue, 9 Jun 2026 15:55:01 +0300 Subject: [PATCH 28/28] enter --- EXILED/Exiled.API/Features/Items/Firearm.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 77d552c9e4..4aea76359b 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -818,13 +818,12 @@ public bool PlaySound(int index, MixerChannel channel, float? range = null, floa /// /// Rpc header for fire type like fire or dry fire. /// The number of chambers fired. - public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chambersFired = 1) + /// The index of the sound to play. 0 is DryFire, 1 is default gunshot. + public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chambersFired = 1, byte soundIndex = 1) { - // Todo: Get it from GunSounTypes instead of hardcoding it when pr 808 merged. - PlaySound(1, MixerChannel.Weapons, AudioModule.FinalGunshotRange, AudioModule.RandomPitch); + PlaySound(soundIndex, MixerChannel.Weapons, AudioModule.FinalGunshotRange, AudioModule.RandomPitch); -#pragma warning disable IDE0031 // Use null propagation - if (AutomaticActionModule != null) + if (AutomaticActionModule?.gameObject != null) { AutomaticActionModule.SendRpc( writer => @@ -835,7 +834,6 @@ public void FakeFire(MessageHeader rpcHeader = MessageHeader.RpcFire, byte chamb }, true); } -#pragma warning restore IDE0031 // Use null propagation if (ImpactEffectsModule != null) {