From 244d90eee18050374af5f489ca6375dd35999d1c Mon Sep 17 00:00:00 2001
From: ZeroTwo <63092138+NotZer0Two@users.noreply.github.com>
Date: Fri, 19 Jul 2024 21:35:58 +0200
Subject: [PATCH] `[Exiled::API]` Rework MirrorExtension (#2731)
* Added UsersWhitelisted in Server.cs
* MirrorExtensions Rework
* Removed wrong import
* Rework
* Fixed issue and renamed the functions
* Fixed Stylecop and issue with building
* Fixed Name
* Changed Name from UsersWhitelisted to WhitelistedPlayers
---------
Co-authored-by: xNexusACS <83370388+xNexusACS@users.noreply.github.com>
---
Exiled.API/Extensions/ItemExtensions.cs | 3 +
Exiled.API/Extensions/MirrorExtensions.cs | 229 +++-------------------
Exiled.API/Extensions/RoomExtensions.cs | 3 +-
Exiled.API/Features/Intercom.cs | 13 ++
Exiled.API/Features/Player.cs | 166 +++++++++++++++-
Exiled.API/Features/Roles/FpcRole.cs | 1 -
Exiled.API/Features/Room.cs | 14 ++
Exiled.API/Features/Server.cs | 2 +-
8 files changed, 228 insertions(+), 203 deletions(-)
diff --git a/Exiled.API/Extensions/ItemExtensions.cs b/Exiled.API/Extensions/ItemExtensions.cs
index f55754b835..bae5b06c6c 100644
--- a/Exiled.API/Extensions/ItemExtensions.cs
+++ b/Exiled.API/Extensions/ItemExtensions.cs
@@ -15,11 +15,14 @@ namespace Exiled.API.Extensions
using Features.Items;
using InventorySystem;
using InventorySystem.Items;
+ using InventorySystem.Items.Firearms;
using InventorySystem.Items.Firearms.Attachments;
using InventorySystem.Items.Pickups;
using InventorySystem.Items.ThrowableProjectiles;
using Structs;
+ using Firearm = Features.Items.Firearm;
+
///
/// A set of extensions for .
///
diff --git a/Exiled.API/Extensions/MirrorExtensions.cs b/Exiled.API/Extensions/MirrorExtensions.cs
index d92ab75709..aa38b362e9 100644
--- a/Exiled.API/Extensions/MirrorExtensions.cs
+++ b/Exiled.API/Extensions/MirrorExtensions.cs
@@ -144,204 +144,6 @@ public static ReadOnlyDictionary RpcFullNames
///
public static MethodInfo SendSpawnMessageMethodInfo => sendSpawnMessageMethodInfoValue ??= typeof(NetworkServer).GetMethod("SendSpawnMessage", BindingFlags.NonPublic | BindingFlags.Static);
- ///
- /// Plays a beep sound that only the target can hear.
- ///
- /// Target to play sound to.
- public static void PlayBeepSound(this Player player) => SendFakeTargetRpc(player, ReferenceHub.HostHub.networkIdentity, typeof(AmbientSoundPlayer), nameof(AmbientSoundPlayer.RpcPlaySound), 7);
-
- ///
- /// Set on the player that only the can see.
- ///
- /// Only this player can see info.
- /// Target to set info.
- /// Setting info.
- public static void SetPlayerInfoForTargetOnly(this Player player, Player target, string info) => player.SendFakeSyncVar(target.ReferenceHub.networkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_customPlayerInfoString), info);
-
- ///
- /// Plays a gun sound that only the can hear.
- ///
- /// Target to play.
- /// Position to play on.
- /// Weapon' sound to play.
- /// Sound's volume to set.
- /// GunAudioMessage's audioClipId to set (default = 0).
- public static void PlayGunSound(this Player player, Vector3 position, ItemType itemType, byte volume, byte audioClipId = 0)
- {
- GunAudioMessage message = new()
- {
- Weapon = itemType,
- AudioClipId = audioClipId,
- MaxDistance = volume,
- ShooterHub = player.ReferenceHub,
- ShooterPosition = new RelativePosition(position),
- };
-
- player.Connection.Send(message);
- }
-
- ///
- /// Sets of a that only the player can see.
- ///
- /// Room to modify.
- /// Only this player can see room color.
- /// Color to set.
- public static void SetRoomColorForTargetOnly(this Room room, Player target, Color color) => target.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkOverrideColor), color);
-
- ///
- /// Sets the lights of a to be either on or off, visible only to the player.
- ///
- /// The room to modify the lights of.
- /// The player who will see the lights state change.
- /// The state to set the lights to. True for on, false for off.
- public static void SetRoomLightsForTargetOnly(this Room room, Player target, bool value) => target.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkLightsEnabled), value);
-
- ///
- /// Sets that only the player can see.
- ///
- /// Only this player can see Display Text.
- /// Text displayed to the player.
- public static void SetIntercomDisplayTextForTargetOnly(this Player target, string text) => target.SendFakeSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText), text);
-
- ///
- /// Resync .
- ///
- public static void ResetIntercomDisplayText() => ResyncSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText));
-
- ///
- /// Sets of a that only the player can see.
- ///
- /// Only this player can see the name changed.
- /// Player that will desync the CustomName.
- /// Nickname to set.
- public static void SetName(this Player target, Player player, string name)
- {
- target.SendFakeSyncVar(player.NetworkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_displayName), name);
- }
-
- ///
- /// Change character model for appearance.
- /// It will continue until 's changes.
- ///
- /// Player to change.
- /// Model type.
- /// Whether or not to skip the little jump that works around an invisibility issue.
- /// The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).
- public static void ChangeAppearance(this Player player, RoleTypeId type, bool skipJump = false, byte unitId = 0) => ChangeAppearance(player, type, Player.List.Where(x => x != player), skipJump, unitId);
-
- ///
- /// Change character model for appearance.
- /// It will continue until 's changes.
- ///
- /// Player to change.
- /// Model type.
- /// The players who should see the changed appearance.
- /// Whether or not to skip the little jump that works around an invisibility issue.
- /// The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).
- public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumerable playersToAffect, bool skipJump = false, byte unitId = 0)
- {
- if (!player.IsConnected || !RoleExtensions.TryGetRoleBase(type, out PlayerRoleBase roleBase))
- return;
-
- bool isRisky = type.GetTeam() is Team.Dead || player.IsDead;
-
- NetworkWriterPooled writer = NetworkWriterPool.Get();
- writer.WriteUShort(38952);
- writer.WriteUInt(player.NetId);
- writer.WriteRoleType(type);
-
- if (roleBase is HumanRole humanRole && humanRole.UsesUnitNames)
- {
- if (player.Role.Base is not HumanRole)
- isRisky = true;
- writer.WriteByte(unitId);
- }
-
- if (roleBase is FpcStandardRoleBase fpc)
- {
- if (player.Role.Base is not FpcStandardRoleBase playerfpc)
- isRisky = true;
- else
- fpc = playerfpc;
-
- ushort value = 0;
- fpc?.FpcModule.MouseLook.GetSyncValues(0, out value, out ushort _);
- writer.WriteRelativePosition(player.RelativePosition);
- writer.WriteUShort(value);
- }
-
- if (roleBase is ZombieRole)
- {
- if (player.Role.Base is not ZombieRole)
- isRisky = true;
-
- writer.WriteUShort((ushort)Mathf.Clamp(Mathf.CeilToInt(player.MaxHealth), ushort.MinValue, ushort.MaxValue));
- }
-
- foreach (Player target in playersToAffect)
- {
- if (target != player || !isRisky)
- target.Connection.Send(writer.ToArraySegment());
- else
- Log.Error($"Prevent Seld-Desync of {player.Nickname} with {type}");
- }
-
- NetworkWriterPool.Return(writer);
-
- // To counter a bug that makes the player invisible until they move after changing their appearance, we will teleport them upwards slightly to force a new position update for all clients.
- if (!skipJump)
- player.Position += Vector3.up * 0.25f;
- }
-
- ///
- /// Send CASSIE announcement that only can hear.
- ///
- /// Target to send.
- /// Announcement words.
- /// Same on 's isHeld.
- /// Same on 's isNoisy.
- /// Same on 's isSubtitles.
- public static void PlayCassieAnnouncement(this Player player, string words, bool makeHold = false, bool makeNoise = true, bool isSubtitles = false)
- {
- foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers)
- {
- if (controller != null)
- {
- SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), words, makeHold, makeNoise, isSubtitles);
- }
- }
- }
-
- ///
- /// Send CASSIE announcement with custom subtitles for translation that only can hear and see it.
- ///
- /// Target to send.
- /// The message to be reproduced.
- /// The translation should be show in the subtitles.
- /// Same on 's isHeld.
- /// Same on 's isNoisy.
- /// Same on 's isSubtitles.
- public static void MessageTranslated(this Player player, string words, string translation, bool makeHold = false, bool makeNoise = true, bool isSubtitles = true)
- {
- StringBuilder announcement = StringBuilderPool.Pool.Get();
-
- string[] cassies = words.Split('\n');
- string[] translations = translation.Split('\n');
-
- for (int i = 0; i < cassies.Length; i++)
- announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} ");
-
- string message = StringBuilderPool.Pool.ToStringReturn(announcement);
-
- foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers)
- {
- if (controller != null)
- {
- SendFakeTargetRpc(player, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), message, makeHold, makeNoise, isSubtitles);
- }
- }
- }
-
///
/// Send fake values to client's .
///
@@ -412,6 +214,37 @@ public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwne
NetworkWriterPool.Return(writer);
}
+ ///
+ /// Send fake values to client's .
+ ///
+ /// Target to send.
+ /// of object that owns .
+ /// 's type.
+ /// Property name starting with Rpc.
+ /// Values of send to target.
+ public static void SendFakeTargetRpc(ReferenceHub target, NetworkIdentity behaviorOwner, Type targetType, string rpcName, params object[] values)
+ {
+ if (target.gameObject == null)
+ return;
+
+ NetworkWriterPooled writer = NetworkWriterPool.Get();
+
+ foreach (object value in values)
+ WriterExtensions[value.GetType()].Invoke(null, new[] { writer, value });
+
+ RpcMessage msg = new()
+ {
+ netId = behaviorOwner.netId,
+ componentIndex = (byte)GetComponentIndex(behaviorOwner, targetType),
+ functionHash = (ushort)RpcFullNames[$"{targetType.Name}.{rpcName}"].GetStableHashCode(),
+ payload = writer.ToArraySegment(),
+ };
+
+ target.connectionToClient.Send(msg);
+
+ NetworkWriterPool.Return(writer);
+ }
+
///
/// Send fake values to client's .
///
diff --git a/Exiled.API/Extensions/RoomExtensions.cs b/Exiled.API/Extensions/RoomExtensions.cs
index 796a9895b5..f4ccc5272b 100644
--- a/Exiled.API/Extensions/RoomExtensions.cs
+++ b/Exiled.API/Extensions/RoomExtensions.cs
@@ -8,8 +8,9 @@
namespace Exiled.API.Extensions
{
using Exiled.API.Enums;
-
+ using Exiled.API.Features;
using MapGeneration;
+ using UnityEngine;
///
/// A set of extensions for and .
diff --git a/Exiled.API/Features/Intercom.cs b/Exiled.API/Features/Intercom.cs
index 5dbee72414..b3dc5af812 100644
--- a/Exiled.API/Features/Intercom.cs
+++ b/Exiled.API/Features/Intercom.cs
@@ -7,6 +7,7 @@
namespace Exiled.API.Features
{
+ using Exiled.API.Extensions;
using Mirror;
using PlayerRoles.Voice;
@@ -97,5 +98,17 @@ public static float SpeechRemainingTime
/// Times out the intercom.
///
public static void Timeout() => State = IntercomState.Cooldown;
+
+ ///
+ /// Sets that only the player can see.
+ ///
+ /// Only this player can see Display Text.
+ /// Text displayed to the player.
+ public static void SetIntercomDisplayTextForTargetOnly(this Player target, string text) => target.SendFakeSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText), text);
+
+ ///
+ /// Resync .
+ ///
+ public static void ResetIntercomDisplayText() => MirrorExtensions.ResyncSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText));
}
}
\ No newline at end of file
diff --git a/Exiled.API/Features/Player.cs b/Exiled.API/Features/Player.cs
index d2705f7aa6..f194c827da 100644
--- a/Exiled.API/Features/Player.cs
+++ b/Exiled.API/Features/Player.cs
@@ -12,6 +12,7 @@ namespace Exiled.API.Features
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
+ using System.Text;
using Core;
using CustomPlayerEffects;
@@ -51,6 +52,7 @@ namespace Exiled.API.Features
using PlayerRoles;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps;
+ using PlayerRoles.PlayableScps.Scp049.Zombies;
using PlayerRoles.RoleAssign;
using PlayerRoles.Spectating;
using PlayerRoles.Voice;
@@ -58,6 +60,7 @@ namespace Exiled.API.Features
using PluginAPI.Core;
using RelativePositioning;
using RemoteAdmin;
+ using Respawning;
using Respawning.NamingRules;
using RoundRestarting;
using UnityEngine;
@@ -73,6 +76,7 @@ namespace Exiled.API.Features
using Firearm = Items.Firearm;
using FirearmPickup = Pickups.FirearmPickup;
using HumanRole = Roles.HumanRole;
+ using PHumanRole = PlayerRoles.HumanRole;
///
/// Represents the in-game player, by encapsulating a .
@@ -3733,9 +3737,9 @@ public void Reconnect(ushort newPort = 0, float delay = 5, bool reconnect = true
Connection.Send(new RoundRestartMessage(roundRestartType, delay, newPort, reconnect, false));
}
- ///
+ ///
public void PlayGunSound(ItemType type, byte volume, byte audioClipId = 0) =>
- MirrorExtensions.PlayGunSound(this, Position, type, volume, audioClipId);
+ PlayGunSound(Position, type, volume, audioClipId);
///
public void PlaceBlood(Vector3 direction) => Map.PlaceBlood(Position, direction);
@@ -3923,6 +3927,164 @@ public void SetRank(string rankName, string rankColor)
RankColor = rankColor;
}
+ ///
+ /// Plays a beep sound that only the player can hear.
+ ///
+ public void PlayBeepSound() => MirrorExtensions.SendFakeTargetRpc(ReferenceHub, ReferenceHub.HostHub.networkIdentity, typeof(AmbientSoundPlayer), nameof(AmbientSoundPlayer.RpcPlaySound), 7);
+
+ ///
+ /// Plays a gun sound that only the target can hear.
+ ///
+ /// Position to play on.
+ /// Weapon' sound to play.
+ /// Sound's volume to set.
+ /// GunAudioMessage's audioClipId to set (default = 0).
+ public void PlayGunSound(Vector3 position, ItemType itemType, byte volume, byte audioClipId = 0)
+ {
+ GunAudioMessage message = new()
+ {
+ Weapon = itemType,
+ AudioClipId = audioClipId,
+ MaxDistance = volume,
+ ShooterHub = ReferenceHub,
+ ShooterPosition = new RelativePosition(position),
+ };
+
+ Connection.Send(message);
+ }
+
+ ///
+ /// Set to the player that only can see.
+ ///
+ /// Target to set info.
+ /// Setting info.
+ public void SetPlayerInfoForTargetOnly(Player target, string info) => MirrorExtensions.SendFakeTargetRpc(ReferenceHub, target.ReferenceHub.networkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_customPlayerInfoString), info);
+
+ ///
+ /// Set to the player that only can see.
+ ///
+ /// Target to set info.
+ /// Nickname to set.
+ public void SetNameForTargetOnly(Player target, string name) => target.SendFakeSyncVar(NetworkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_displayName), name);
+
+ ///
+ /// Change character model for appearance.
+ /// It will continue until 's changes.
+ ///
+ /// Model type.
+ /// Whether or not to skip the little jump that works around an invisibility issue.
+ /// The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).
+ public void ChangeAppearance(RoleTypeId type, bool skipJump = false, byte unitId = 0) => ChangeAppearance(type, List.Where(x => x.ReferenceHub != ReferenceHub), skipJump, unitId);
+
+ ///
+ /// Change character model for appearance.
+ /// It will continue until 's changes.
+ ///
+ /// Model type.
+ /// The players who should see the changed appearance.
+ /// Whether or not to skip the little jump that works around an invisibility issue.
+ /// The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).
+ public void ChangeAppearance(RoleTypeId type, IEnumerable playersToAffect, bool skipJump = false, byte unitId = 0)
+ {
+ if (ReferenceHub.gameObject == null || !RoleExtensions.TryGetRoleBase(type, out PlayerRoleBase roleBase))
+ return;
+ bool isRisky = type.GetRoleBase().Team is Team.Dead || IsDead;
+
+ NetworkWriterPooled writer = NetworkWriterPool.Get();
+ writer.WriteUShort(38952);
+ writer.WriteUInt(NetId);
+ writer.WriteRoleType(type);
+
+ if (roleBase is PHumanRole humanRole && humanRole.UsesUnitNames)
+ {
+ if (Role.Base is not PHumanRole)
+ isRisky = true;
+ writer.WriteByte(unitId);
+ }
+
+ if (roleBase is FpcStandardRoleBase fpc)
+ {
+ if (Role.Base is not FpcStandardRoleBase playerfpc)
+ isRisky = true;
+ else
+ fpc = playerfpc;
+
+ ushort value = 0;
+ fpc?.FpcModule.MouseLook.GetSyncValues(0, out value, out ushort _);
+ writer.WriteRelativePosition(RelativePosition);
+ writer.WriteUShort(value);
+ }
+
+ if (roleBase is ZombieRole)
+ {
+ if (Role.Base is not ZombieRole)
+ isRisky = true;
+
+ writer.WriteUShort((ushort)Mathf.Clamp(Mathf.CeilToInt(MaxHealth), ushort.MinValue, ushort.MaxValue));
+ }
+
+ foreach (Player target in playersToAffect)
+ {
+ if (target.ReferenceHub != ReferenceHub || !isRisky)
+ target.Connection.Send(writer.ToArraySegment());
+ else
+ Log.Error($"Prevent Seld-Desync of {Nickname} with {type}");
+ }
+
+ NetworkWriterPool.Return(writer);
+
+ // To counter a bug that makes the player invisible until they move after changing their appearance, we will teleport them upwards slightly to force a new position update for all clients.
+ if (!skipJump)
+ Position += Vector3.up * 0.25f;
+ }
+
+ ///
+ /// Send CASSIE announcement that only can hear.
+ ///
+ /// Announcement words.
+ /// Same on 's isHeld.
+ /// Same on 's isNoisy.
+ /// Same on 's isSubtitles.
+ public void PlayCassieAnnouncement(string words, bool makeHold = false, bool makeNoise = true, bool isSubtitles = false)
+ {
+ foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers)
+ {
+ if (controller != null)
+ {
+ MirrorExtensions.SendFakeTargetRpc(ReferenceHub, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), words, makeHold, makeNoise, isSubtitles);
+ }
+ }
+ }
+
+ ///
+ /// Send CASSIE announcement with custom subtitles for translation that only can hear and see it.
+ ///
+ /// The message to be reproduced.
+ /// The translation should be show in the subtitles.
+ /// Same on 's isHeld.
+ /// Same on 's isNoisy.
+ /// Same on 's isSubtitles.
+ public void SendCassieAnnouncement(string words, string translation, bool makeHold = false, bool makeNoise = true, bool isSubtitles = true)
+ {
+ StringBuilder announcement = StringBuilderPool.Pool.Get();
+
+ string[] cassies = words.Split('\n');
+ string[] translations = translation.Split('\n');
+
+ for (int i = 0; i < cassies.Length; i++)
+ announcement.Append($"{translations[i].Replace(' ', ' ')} {cassies[i]} ");
+
+ string message = StringBuilderPool.Pool.ToStringReturn(announcement);
+
+ foreach (RespawnEffectsController controller in RespawnEffectsController.AllControllers)
+ {
+ if (controller != null)
+ {
+ MirrorExtensions.SendFakeTargetRpc(ReferenceHub, controller.netIdentity, typeof(RespawnEffectsController), nameof(RespawnEffectsController.RpcCassieAnnouncement), message, makeHold, makeNoise, isSubtitles);
+ }
+ }
+ }
+
///
/// Converts the player in a human-readable format.
///
diff --git a/Exiled.API/Features/Roles/FpcRole.cs b/Exiled.API/Features/Roles/FpcRole.cs
index 020efd254d..aa68d62088 100644
--- a/Exiled.API/Features/Roles/FpcRole.cs
+++ b/Exiled.API/Features/Roles/FpcRole.cs
@@ -10,7 +10,6 @@ namespace Exiled.API.Features.Roles
using System.Collections.Generic;
using System.Reflection;
- using Exiled.API.Extensions;
using Exiled.API.Features.Core.Attributes;
using Exiled.API.Features.Core.Generic.Pools;
using HarmonyLib;
diff --git a/Exiled.API/Features/Room.cs b/Exiled.API/Features/Room.cs
index 4ccd72b48a..93ee33f6bd 100644
--- a/Exiled.API/Features/Room.cs
+++ b/Exiled.API/Features/Room.cs
@@ -424,6 +424,20 @@ public void UnlockAll()
///
public void ResetColor() => Color = Color.clear;
+ ///
+ /// Sets of the room that only the player can see.
+ ///
+ /// Only this player can see room color.
+ /// Color to set.
+ public void SetColorForTargetOnly(Player target, Color color) => target.SendFakeSyncVar(RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkOverrideColor), color);
+
+ ///
+ /// Sets the lights of the room to be either on or off, visible only to the player.
+ ///
+ /// The player who will see the lights state change.
+ /// The state to set the lights to. True for on, false for off.
+ public void SetLightsForTargetOnly(Player target, bool value) => target.SendFakeSyncVar(RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkLightsEnabled), value);
+
///
/// Returns the Room in a human-readable format.
///
diff --git a/Exiled.API/Features/Server.cs b/Exiled.API/Features/Server.cs
index 355925d2cc..a2276ea44e 100644
--- a/Exiled.API/Features/Server.cs
+++ b/Exiled.API/Features/Server.cs
@@ -183,7 +183,7 @@ public static bool IsWhitelisted
///
/// Gets the List of player currently whitelisted.
///
- public static HashSet UsersWhitelisted => WhiteList.Users;
+ public static HashSet WhitelistedPlayers => WhiteList.Users;
///
/// Gets a value indicating whether or not this server is verified.