Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 54 additions & 0 deletions EXILED/Exiled.API/Enums/WearableElementType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// -----------------------------------------------------------------------
// <copyright file="WearableElementType.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Enums
{
using System;

/// <summary>
/// An enum containing all types of Wearable elements.
/// </summary>
[Flags]
public enum WearableElementType
{
/// <summary>
/// No wearable elements.
/// </summary>
None = 0,

/// <summary>
/// SCP-268 wearable element.
/// </summary>
Scp268Hat = 1,

/// <summary>
/// SCP-1344 wearable element.
/// </summary>
Scp1344Goggles = 2,

/// <summary>
/// Armor wearable element.
/// <remarks>if armor is not specified it's will choose the one from Inventory</remarks>
/// </summary>
ArmorDefault = 4,

/// <summary>
/// Force the Light armor wearable element.
/// </summary>
ArmorLight = ArmorDefault | 8,

/// <summary>
/// Force the Combat armor wearable element.
/// </summary>
ArmorCombat = ArmorDefault | 16,

/// <summary>
/// Force the Heavy armor wearable element.
/// </summary>
ArmorHeavy = ArmorDefault | 32,
}
}
15 changes: 15 additions & 0 deletions EXILED/Exiled.API/Extensions/ItemExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,21 @@ public static int GetMaxAmmo(this FirearmType item)
_ => FirearmType.None,
};

/// <summary>
/// Converts a valid firearm <see cref="ItemType"/> into a <see cref="WearableElementType"/>.
/// </summary>
/// <param name="type">The <see cref="ItemType"/> to convert.</param>
/// <returns>The firearm type of the given item.</returns>
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,
};

/// <summary>
/// Converts an <see cref="AmmoType"/> into it's corresponding <see cref="ItemType"/>.
/// </summary>
Expand Down
79 changes: 79 additions & 0 deletions EXILED/Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -76,6 +78,8 @@ namespace Exiled.API.Features

using RoundRestarting;

using Unity.Collections.LowLevel.Unsafe;

using UnityEngine;

using Utils;
Expand Down Expand Up @@ -855,6 +859,67 @@ public VoiceChatChannel VoiceChannel
}
}

/// <summary>
/// Gets or sets the player's wearable elements.
/// </summary>
/// <seealso cref="EnableWearables"/> <seealso cref="DisableWearables"/>
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<byte, sbyte>(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);
}
}

/// <summary>
/// Gets a value indicating whether the player is transmitting on a Radio.
/// </summary>
Expand Down Expand Up @@ -2528,6 +2593,20 @@ public void Broadcast(ushort duration, string message, global::Broadcast.Broadca
/// </summary>
public void ClearBroadcasts() => Server.Broadcast.TargetClearElements(Connection);

/// <summary>
/// Enables the specified <see cref="WearableElements"/> on the player.
/// </summary>
/// <param name="wearableElements">The <see cref="WearableElements"/> flags to enable.</param>
/// <seealso cref="DisableWearables"/>
public void EnableWearables(WearableElementType wearableElements) => Wearables |= wearableElements;

/// <summary>
/// Disables the specified <see cref="WearableElements"/> on the player.
/// </summary>
/// <param name="wearableElements">The <see cref="WearableElements"/> flags to disable.</param>
/// <seealso cref="EnableWearables"/>
public void DisableWearables(WearableElementType wearableElements) => Wearables &= ~wearableElements;

/// <summary>
/// Adds the amount of a specified <see cref="AmmoType">ammo type</see> to the player's inventory.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// -----------------------------------------------------------------------
// <copyright file="ChangingWearablesEventArgs.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

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;

/// <summary>
/// Contains all information before new information about wearables is sent to clients.
/// </summary>
public class ChangingWearablesEventArgs : IPlayerEvent, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="ChangingWearablesEventArgs"/> class.
/// </summary>
/// <param name="player"><inheritdoc cref="Player"/></param>
/// <param name="newWearables"><inheritdoc cref="NewWearables"/></param>
/// <param name="isAllowed"><inheritdoc cref="IsAllowed"/></param>
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<byte, sbyte>(ref WearableSync.PayloadWriter.buffer[0]);
exiledFlags = armor.GetWearableElementType();
}

NewWearables = (WearableElementType)newWearables | exiledFlags;
}

/// <inheritdoc/>
public Player Player { get; }

/// <summary>
/// Gets or sets new wearables that'll be displayed on <see cref="Player"/>.
/// </summary>
public WearableElementType NewWearables { get; set; }

/// <inheritdoc/>
public bool IsAllowed { get; set; }
}
}
13 changes: 12 additions & 1 deletion EXILED/Exiled.Events/Handlers/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,11 @@ public class Player
/// </summary>
public static Event<Scp1576TransmissionEndedEventArgs> Scp1576TransmissionEnded { get; set; } = new();

/// <summary>
/// Invoked before new information about wearables is sent to clients.
/// </summary>
public static Event<ChangingWearablesEventArgs> ChangingWearables { get; set; } = new();

/// <summary>
/// Called before a player's emotion changed.
/// </summary>
Expand Down Expand Up @@ -1440,9 +1445,15 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
public static void OnInteractingEmergencyButton(InteractingEmergencyButtonEventArgs ev) => InteractingEmergencyButton.InvokeSafely(ev);

/// <summary>
/// Called after a 1576 transmisiion has ended.
/// Called after a 1576 transmission has ended.
/// </summary>
/// <param name="ev">The <see cref="Scp1576TransmissionEndedEventArgs"/> instance.</param>
public static void OnScp1576TransmissionEnded(Scp1576TransmissionEndedEventArgs ev) => Scp1576TransmissionEnded.InvokeSafely(ev);

/// <summary>
/// Called before new information about wearables is sent to clients.
/// </summary>
/// <param name="ev">The <see cref="ChangingWearablesEventArgs"/> instance.</param>
public static void OnChangingWearables(ChangingWearablesEventArgs ev) => ChangingWearables.InvokeSafely(ev);
}
}
120 changes: 120 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Player/ChangingWearables.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// -----------------------------------------------------------------------
// <copyright file="ChangingWearables.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

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;

/// <summary>
/// Patches <see cref="WearableSync.OverrideWearables"/>
/// to add <see cref="Handlers.Player.ChangingWearables"/> event.
/// </summary>
[EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ChangingWearables))]
[HarmonyPatch(typeof(WearableSync), nameof(WearableSync.OverrideWearables))]
internal static class ChangingWearables
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.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<CodeInstruction>.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;
}
}
}
Loading
Loading