Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
73f4de2
Decals
MS-crew Jun 2, 2026
70c2e5b
Update EXILED/Exiled.API/Features/Map.cs
MS-crew Jun 3, 2026
ae154b2
consitent
MS-crew Jun 3, 2026
258abed
Merge branch 'DecalSpawn' of https://github.com/MS-crew/EXILEDPR into…
MS-crew Jun 3, 2026
7f35729
refactor: CustomScpTermination (#629)
XingYeNotFish Jun 4, 2026
c91697c
obsulutes
MS-crew Jun 4, 2026
d254366
Merge branch 'master' into DecalSpawn
MS-crew Jun 4, 2026
af2ec1a
directly usin rpcs
MS-crew Jun 4, 2026
a9d58b2
fix increment & helper methods
MS-crew Jun 5, 2026
9c075f2
Revert "fix increment & helper methods"
MS-crew Jun 5, 2026
eccf7d7
suggestion
louis1706 Jun 6, 2026
1dcd166
Merge pull request #1 from louis1706/suggestion
MS-crew Jun 6, 2026
e488ff9
update
MS-crew Jun 6, 2026
c768459
fix
MS-crew Jun 7, 2026
b74a19e
removing
MS-crew Jun 7, 2026
191f17f
movevininggg
MS-crew Jun 7, 2026
eb6bfbf
Indicator method
MS-crew Jun 7, 2026
92cf98c
Where did this come from?
MS-crew Jun 7, 2026
ec87764
sound & fake shoot
MS-crew Jun 7, 2026
12d5e24
update
MS-crew Jun 8, 2026
81ed9ea
Merge branch 'dev' into DecalSpawn
MS-crew Jun 8, 2026
cd07ff6
gix
MS-crew Jun 8, 2026
2b1c5ae
docs & wrong if check
MS-crew Jun 8, 2026
7e2bf46
cahced variables
MS-crew Jun 8, 2026
f316fec
renaming
MS-crew Jun 8, 2026
b5187e8
dellete
MS-crew Jun 8, 2026
e7342c2
i realized there is double, pump ,disruptor action modulle too so i r…
MS-crew Jun 8, 2026
f591c6e
f
MS-crew Jun 8, 2026
d3d1424
fuck it let be longht
MS-crew Jun 8, 2026
5f791fe
Valera was right its basic rpcs we dont need its
MS-crew Jun 9, 2026
1e4d70d
fix
MS-crew Jun 9, 2026
894b4af
enter
MS-crew Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions EXILED/Exiled.API/Enums/BloodType.cs

This file was deleted.

235 changes: 214 additions & 21 deletions EXILED/Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ namespace Exiled.API.Extensions

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;

Expand Down Expand Up @@ -56,6 +59,8 @@ namespace Exiled.API.Extensions

using Utils.Networking;

using Firearm = Features.Items.Firearm;

/// <summary>
/// A set of extensions for <see cref="Mirror"/> Networking.
/// </summary>
Expand Down Expand Up @@ -211,58 +216,245 @@ public static ReadOnlyDictionary<string, string> RpcFullNames
/// </summary>
/// <param name="player">Target to play.</param>
/// <param name="position">Position to play on.</param>
/// <param name="itemType">Weapon' sound to play.</param>
/// <param name="volume">Sound's volume to set.</param>
/// <param name="audioClipId">GunAudioMessage's audioClipId to set (default = 0).</param>
[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);
/// <param name="firearmType">Weapon's sound to play.</param>
/// <param name="pitch">Speed of sound.</param>
/// <param name="clipIndex">Index of clip.</param>
[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);

/// <summary>
/// Plays a gun sound that only the <paramref name="player"/> can hear.
/// </summary>
/// <param name="player">Target to play.</param>
/// <param name="position">Position to play on.</param>
/// <param name="firearmType">Weapon's sound to play.</param>
/// <param name="pitch">Speed of sound.</param>
/// <param name="clipIndex">Index of clip.</param>
public static void PlayGunSound(this Player player, Vector3 position, FirearmType firearmType, float pitch = 1, int clipIndex = 0)
/// <param name="position">Position to play on.</param>
/// <param name="mixerChannel">Audio's mixer channel.</param>
/// <param name="range">Max range of sound.</param>
/// <param name="pitch">Speed of sound.</param>
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.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<Firearm>(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())
{
writer.WriteUShort(NetworkMessageId<RoleSyncInfo>.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);
}

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(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));
});
}

/// <summary>
/// Plays a gun sound to the specified player.
/// </summary>
/// <param name="firearm">The firearm whose <see cref="AudioModule"/> to use.</param>
/// <param name="target">The player to send the sound to.</param>
/// <param name="index">The index of the audio clip to play.</param>
/// <param name="channel">The <see cref="MixerChannel"/> to play the sound on.</param>
/// <param name="position">The world position the sound originates from.</param>
/// <param name="shooterVisible">Whether the shooter is visible to the target. If <see langword="false"/>, the sound will be played at <paramref name="position"/> instead of on the firearm's transform.</param>
/// <param name="range">The range of the sound.</param>
/// <param name="pitch">The pitch of the sound.</param>
/// <returns><see langword="true"/> if the sound was played successfully; <see langword="false"/> if <see cref="AudioModule"/> is <see langword="null"/>.</returns>
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)
{
Log.Error($"Firearm {firearm} doesn't have an audio module.");
return false;
}

if (target == null)
{
Log.Error("Target player is null.");
return false;
}

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;
}

/// <summary>
/// Plays a gun sound to the specified players.
/// </summary>
/// <param name="firearm">The firearm whose <see cref="AudioModule"/> to use.</param>
/// <param name="targets">The players to send the sound to.</param>
/// <param name="index">The index of the audio clip to play.</param>
/// <param name="channel">The <see cref="MixerChannel"/> to play the sound on.</param>
/// <param name="position">The world position the sound originates from.</param>
/// <param name="shooterVisible">Whether the shooter is visible to the target. If <see langword="false"/>, the sound will be played at <paramref name="position"/> instead of on the firearm's transform.</param>
/// <param name="range">The range of the sound.</param>
/// <param name="pitch">The pitch of the sound.</param>
/// <returns><see langword="true"/> if the sound was played successfully; <see langword="false"/> if <see cref="AudioModule"/> is <see langword="null"/>.</returns>
public static bool PlaySound(this Firearm firearm, IEnumerable<Player> targets, int index, MixerChannel channel, Vector3 position, bool shooterVisible, float? range = null, float? pitch = null)
{
AudioModule audioModule = firearm.AudioModule;
if (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;
}

if (!range.HasValue)
range = audioModule.FinalGunshotRange;

if (!pitch.HasValue)
pitch = audioModule.RandomPitch;

HashSet<ReferenceHub> targetHubs = targets.Select(p => p.ReferenceHub).ToHashSet();

audioModule.SendRpc(targetHubs.Contains, writer => audioModule.ServerSend(writer, index, pitch.Value, channel, range.Value, position, shooterVisible));
return true;
}

/// <summary>
/// Spawns a blood decal for this player.
/// </summary>
/// <param name="player">Target to spawn blood decal for.</param>
/// <param name="position">The position of the blood decal.</param>
/// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
/// <returns><see langword="true"/> if the blood decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
public static bool SpawnBlood(this Player player, Vector3 position, Vector3 sourcePosition) => SpawnDecal(player, position, sourcePosition, DecalPoolType.Blood);

/// <summary>
/// Spawns a blood decal for the specified players.
/// </summary>
/// <param name="players">The players for which to spawn the blood decal.</param>
/// <param name="position">The position of the blood decal.</param>
/// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
/// <returns><see langword="true"/> if the blood decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
public static bool SpawnBlood(this IEnumerable<Player> players, Vector3 position, Vector3 sourcePosition) => SpawnDecal(players, position, sourcePosition, DecalPoolType.Blood, FirearmType.Com15);

/// <summary>
/// Spawns a decal for this player.
/// </summary>
/// <param name="player">Target to spawn decal for.</param>
/// <param name="position">The position of the decal.</param>
/// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
/// <param name="decalType">The <see cref="Decals.DecalPoolType"/>.</param>
/// <param name="firearmType">The <see cref="Enums.FirearmType"/> to use.</param>
/// <returns><see langword="true"/> if the decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
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<Firearm>(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;
}

/// <summary>
/// Spawns a decal for the specified targets.
/// </summary>
/// <param name="targets">The targets for which to spawn the decal.</param>
/// <param name="position">The position of the decal.</param>
/// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
/// <param name="decalType">The <see cref="Decals.DecalPoolType"/>.</param>
/// <param name="firearmType">The <see cref="Enums.FirearmType"/> to use.</param>
/// <returns><see langword="true"/> if the decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
public static bool SpawnDecal(this IEnumerable<Player> 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<Firearm>(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<ReferenceHub> 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;
}

/// <summary>
/// Place blood that only the <paramref name="player"/> can see.
/// </summary>
Expand All @@ -271,6 +463,7 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp
/// <param name="origin">The direction of the blood decal.</param>
/// <param name="roleTypeId">The RoleTypeId from who blood come from.</param>
/// <param name="gettingShotSoundIndex">The sound than player get when getting shot.</param>
[Obsolete("Use Player::SpawnBlood(Vector3, Vector3) instead.")]
#pragma warning disable IDE0060 // TODO: Deleted the unused param
public static void PlaceBlood(this Player player, Vector3 position, Vector3 origin, RoleTypeId roleTypeId, int gettingShotSoundIndex)
#pragma warning restore IDE0060
Expand Down
Loading
Loading