diff --git a/EXILED/Exiled.API/Enums/WearableElementType.cs b/EXILED/Exiled.API/Enums/WearableElementType.cs
new file mode 100644
index 000000000..0704e954f
--- /dev/null
+++ b/EXILED/Exiled.API/Enums/WearableElementType.cs
@@ -0,0 +1,54 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Enums
+{
+ using System;
+
+ ///
+ /// An enum containing all types of Wearable elements.
+ ///
+ [Flags]
+ public enum WearableElementType
+ {
+ ///
+ /// No wearable elements.
+ ///
+ None = 0,
+
+ ///
+ /// SCP-268 wearable element.
+ ///
+ Scp268Hat = 1,
+
+ ///
+ /// SCP-1344 wearable element.
+ ///
+ Scp1344Goggles = 2,
+
+ ///
+ /// Armor wearable element.
+ /// if armor is not specified it's will choose the one from Inventory
+ ///
+ ArmorDefault = 4,
+
+ ///
+ /// Force the Light armor wearable element.
+ ///
+ ArmorLight = ArmorDefault | 8,
+
+ ///
+ /// Force the Combat armor wearable element.
+ ///
+ ArmorCombat = ArmorDefault | 16,
+
+ ///
+ /// Force the Heavy armor wearable element.
+ ///
+ ArmorHeavy = ArmorDefault | 32,
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs
index 3a4bb791c..d07f9c3a6 100644
--- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs
+++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs
@@ -195,6 +195,21 @@ public static int GetMaxAmmo(this FirearmType item)
_ => FirearmType.None,
};
+ ///
+ /// Converts a valid firearm into a .
+ ///
+ /// The to convert.
+ /// The firearm type of the given item.
+ public static WearableElementType GetWearableElementType(this ItemType type) => type switch
+ {
+ ItemType.SCP268 => WearableElementType.Scp268Hat,
+ ItemType.ArmorLight => WearableElementType.ArmorLight,
+ ItemType.ArmorCombat => WearableElementType.ArmorCombat,
+ ItemType.ArmorHeavy => WearableElementType.ArmorHeavy,
+ ItemType.SCP1344 => WearableElementType.Scp1344Goggles,
+ _ => WearableElementType.None,
+ };
+
///
/// Converts an into it's corresponding .
///
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index a377c0a48..c4da78a8c 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -63,7 +63,9 @@ namespace Exiled.API.Features
using PlayerRoles;
using PlayerRoles.FirstPersonControl;
+ using PlayerRoles.FirstPersonControl.Thirdperson;
using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers;
+ using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables;
using PlayerRoles.RoleAssign;
using PlayerRoles.Spectating;
using PlayerRoles.Voice;
@@ -76,6 +78,8 @@ namespace Exiled.API.Features
using RoundRestarting;
+ using Unity.Collections.LowLevel.Unsafe;
+
using UnityEngine;
using Utils;
@@ -855,6 +859,67 @@ public VoiceChatChannel VoiceChannel
}
}
+ ///
+ /// Gets or sets the player's wearable elements.
+ ///
+ ///
+ public WearableElementType Wearables
+ {
+ get
+ {
+ if (!WearableSync.TryGetData(ReferenceHub, out WearableSyncMessage data))
+ return WearableElementType.None;
+
+ WearableElements flags = data.Flags;
+ WearableElementType exiledFlags = WearableElementType.None;
+
+ if (flags.HasFlagFast(WearableElements.Armor) && data.Payload.Length is 1)
+ {
+ ItemType armor = (ItemType)UnsafeUtility.As(ref data.Payload[0]);
+
+ exiledFlags = armor.GetWearableElementType();
+ }
+
+ return (WearableElementType)flags | exiledFlags;
+ }
+
+ set
+ {
+ if (value is WearableElementType.None)
+ {
+ Log.Info("None");
+
+ WearableSyncMessage wearableSyncMessage = new(ReferenceHub);
+ WearableSync.UpdateDatabaseEntry(wearableSyncMessage);
+ NetworkServer.SendToAll(wearableSyncMessage, 0, false);
+ return;
+ }
+
+ WearableSync.PayloadWriter.Reset();
+ Log.Info("newWearables" + value);
+
+ if (value.HasFlagFast(WearableElementType.ArmorDefault))
+ {
+ ItemType displayedArmor = value.HasFlagFast(WearableElementType.ArmorLight) ? ItemType.ArmorLight :
+ value.HasFlagFast(WearableElementType.ArmorCombat) ? ItemType.ArmorCombat :
+ value.HasFlagFast(WearableElementType.ArmorHeavy) ? ItemType.ArmorHeavy :
+ CurrentArmor?.Type ?? ItemType.None;
+
+ if (displayedArmor is not ItemType.None)
+ WearableSync.PayloadWriter.WriteSByte((sbyte)displayedArmor);
+ else
+ value &= ~WearableElementType.ArmorDefault;
+
+ value &= ~WearableElementType.ArmorLight | WearableElementType.ArmorCombat | WearableElementType.ArmorHeavy;
+ Log.Info("DiplayedArmor" + displayedArmor);
+ }
+
+ WearableSyncMessage wearableSyncMessage2 = new(ReferenceHub, (WearableElements)value, WearableSync.PayloadWriter);
+ WearableSync.UpdateDatabaseEntry(wearableSyncMessage2);
+ NetworkServer.SendToAll(wearableSyncMessage2, 0, false);
+ }
+ }
+
///
/// Gets a value indicating whether the player is transmitting on a Radio.
///
@@ -2528,6 +2593,20 @@ public void Broadcast(ushort duration, string message, global::Broadcast.Broadca
///
public void ClearBroadcasts() => Server.Broadcast.TargetClearElements(Connection);
+ ///
+ /// Enables the specified on the player.
+ ///
+ /// The flags to enable.
+ ///
+ public void EnableWearables(WearableElementType wearableElements) => Wearables |= wearableElements;
+
+ ///
+ /// Disables the specified on the player.
+ ///
+ /// The flags to disable.
+ ///
+ public void DisableWearables(WearableElementType wearableElements) => Wearables &= ~wearableElements;
+
///
/// Adds the amount of a specified ammo type to the player's inventory.
///
diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangingWearablesEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangingWearablesEventArgs.cs
new file mode 100644
index 000000000..6f10ff202
--- /dev/null
+++ b/EXILED/Exiled.Events/EventArgs/Player/ChangingWearablesEventArgs.cs
@@ -0,0 +1,57 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.EventArgs.Player
+{
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+ using Exiled.API.Features;
+ using Exiled.Events.EventArgs.Interfaces;
+
+ using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables;
+
+ using Unity.Collections.LowLevel.Unsafe;
+
+ ///
+ /// Contains all information before new information about wearables is sent to clients.
+ ///
+ public class ChangingWearablesEventArgs : IPlayerEvent, IDeniableEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ public ChangingWearablesEventArgs(Player player, WearableElements newWearables, bool isAllowed = true)
+ {
+ Player = player;
+ IsAllowed = isAllowed;
+
+ WearableElementType exiledFlags = WearableElementType.None;
+
+ if (newWearables.HasFlagFast(WearableElements.Armor) && WearableSync.PayloadWriter.buffer.Length is 1)
+ {
+ ItemType armor = (ItemType)UnsafeUtility.As(ref WearableSync.PayloadWriter.buffer[0]);
+ exiledFlags = armor.GetWearableElementType();
+ }
+
+ NewWearables = (WearableElementType)newWearables | exiledFlags;
+ }
+
+ ///
+ public Player Player { get; }
+
+ ///
+ /// Gets or sets new wearables that'll be displayed on .
+ ///
+ public WearableElementType NewWearables { get; set; }
+
+ ///
+ public bool IsAllowed { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs
index c2ffca618..a8062aa82 100644
--- a/EXILED/Exiled.Events/Handlers/Player.cs
+++ b/EXILED/Exiled.Events/Handlers/Player.cs
@@ -655,6 +655,11 @@ public class Player
///
public static Event Scp1576TransmissionEnded { get; set; } = new();
+ ///
+ /// Invoked before new information about wearables is sent to clients.
+ ///
+ public static Event ChangingWearables { get; set; } = new();
+
///
/// Called before a player's emotion changed.
///
@@ -1440,9 +1445,15 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
public static void OnInteractingEmergencyButton(InteractingEmergencyButtonEventArgs ev) => InteractingEmergencyButton.InvokeSafely(ev);
///
- /// Called after a 1576 transmisiion has ended.
+ /// Called after a 1576 transmission has ended.
///
/// The instance.
public static void OnScp1576TransmissionEnded(Scp1576TransmissionEndedEventArgs ev) => Scp1576TransmissionEnded.InvokeSafely(ev);
+
+ ///
+ /// Called before new information about wearables is sent to clients.
+ ///
+ /// The instance.
+ public static void OnChangingWearables(ChangingWearablesEventArgs ev) => ChangingWearables.InvokeSafely(ev);
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangingWearables.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangingWearables.cs
new file mode 100644
index 000000000..881b8ea90
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangingWearables.cs
@@ -0,0 +1,120 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Events.Player
+{
+ using System.Collections.Generic;
+ using System.Reflection.Emit;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+ using Exiled.API.Features;
+ using Exiled.API.Features.Pools;
+ using Exiled.Events.Attributes;
+ using Exiled.Events.EventArgs.Player;
+
+ using HarmonyLib;
+
+ using Mirror;
+
+ using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patches
+ /// to add event.
+ ///
+ [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ChangingWearables))]
+ [HarmonyPatch(typeof(WearableSync), nameof(WearableSync.OverrideWearables))]
+ internal static class ChangingWearables
+ {
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldarg_1);
+
+ LocalBuilder ev = generator.DeclareLocal(typeof(ChangingWearablesEventArgs));
+
+ Label returnLabel = generator.DefineLabel();
+ newInstructions[^1].labels.Add(returnLabel);
+
+ newInstructions.RemoveAt(index);
+
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ // Player.Get(hub)
+ new(OpCodes.Ldarg_0),
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
+
+ // newWearables
+ new(OpCodes.Ldarg_1),
+
+ // true
+ new(OpCodes.Ldc_I4_1),
+
+ // ChangingWearablesEventArgs ev = new(Player.Get(hub), newWearables, true);
+ new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ChangingWearablesEventArgs))[0]),
+ new(OpCodes.Dup),
+ new(OpCodes.Dup),
+ new(OpCodes.Stloc_S, ev.LocalIndex),
+
+ // Handlers.Player.OnChangingWearables(ev);
+ new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnChangingWearables))),
+
+ // if (!ev.IsAllowed)
+ // return;
+ new(OpCodes.Callvirt, PropertyGetter(typeof(ChangingWearablesEventArgs), nameof(ChangingWearablesEventArgs.IsAllowed))),
+ new(OpCodes.Brfalse_S, returnLabel),
+
+ // push ev.NewWearables to stack for check
+ new(OpCodes.Ldloc_S, ev.LocalIndex),
+ new(OpCodes.Callvirt, PropertyGetter(typeof(ChangingWearablesEventArgs), nameof(ChangingWearablesEventArgs.NewWearables))),
+ });
+
+ int offset = -4;
+ index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0) + offset;
+
+ newInstructions.RemoveRange(index, 4);
+ newInstructions.InsertRange(index, new CodeInstruction[]
+ {
+ // newWearables = ChangingWearables.WriteArmor(ev);
+ new(OpCodes.Ldloc_S, ev.LocalIndex),
+ new(OpCodes.Call, Method(typeof(ChangingWearables), nameof(WriteArmor))),
+ new(OpCodes.Starg_S, 1),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+
+ private static WearableElements WriteArmor(ChangingWearablesEventArgs ev)
+ {
+ WearableElementType value = ev.NewWearables;
+
+ if (value is WearableElementType.None)
+ return WearableElements.None;
+
+ if (value.HasFlagFast(WearableElementType.ArmorDefault))
+ {
+ ItemType displayedArmor = value.HasFlagFast(WearableElementType.ArmorLight) ? ItemType.ArmorLight :
+ value.HasFlagFast(WearableElementType.ArmorCombat) ? ItemType.ArmorCombat :
+ value.HasFlagFast(WearableElementType.ArmorHeavy) ? ItemType.ArmorHeavy :
+ ev.Player.CurrentArmor?.Type ?? ItemType.None;
+
+ WearableSync.PayloadWriter.WriteSByte((sbyte)displayedArmor);
+
+ value &= ~WearableElementType.ArmorLight | WearableElementType.ArmorCombat | WearableElementType.ArmorHeavy;
+ }
+
+ return (WearableElements)value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Generic/WearableArmorPatch.cs b/EXILED/Exiled.Events/Patches/Generic/WearableArmorPatch.cs
new file mode 100644
index 000000000..998e9635d
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Generic/WearableArmorPatch.cs
@@ -0,0 +1,32 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Generic
+{
+ using System.Collections.Generic;
+
+ using HarmonyLib;
+
+ using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers.Wearables;
+
+ ///
+ /// Patches .
+ ///
+ [HarmonyPatch(typeof(WearableSync), nameof(WearableSync.OnHubAdded))]
+ internal static class WearableArmorPatch
+ {
+ private static bool Prefix(ref ReferenceHub hub)
+ {
+ foreach (KeyValuePair item in WearableSync.Database)
+ {
+ hub.connectionToClient.Send(item.Value);
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file