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.