Skip to content
Draft
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
47 changes: 47 additions & 0 deletions EXILED/Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public Player(GameObject gameObject)
DictionaryPool<string, object>.Pool.Return(SessionVariables);
DictionaryPool<RoleTypeId, float>.Pool.Return(FriendlyFireMultiplier);
DictionaryPool<string, Dictionary<RoleTypeId, float>>.Pool.Return(CustomRoleFriendlyFireMultiplier);
ListPool<Func<Player, RoleData>>.Pool.Return(FakeRoleGenerator);
}

/// <summary>
Expand Down Expand Up @@ -407,6 +408,11 @@ public float InfoViewRange
/// </summary>
public Dictionary<string, object> SessionVariables { get; } = DictionaryPool<string, object>.Pool.Get();

/// <summary>
/// Gets a dictionary that contains from this players POV, a dictionary containing other players and their faked roles with custom data.
/// </summary>
public Dictionary<Player, RoleData> FakeRoles { get; } = new();

/// <summary>
/// Gets a value indicating whether the player has Do Not Track (DNT) enabled. If this value is <see langword="true"/>, data about the player unrelated to server security shouldn't be stored.
/// </summary>
Expand Down Expand Up @@ -607,6 +613,12 @@ internal set
}
}

/// <summary>
/// Gets a <see cref="List{T}"/> of <see cref="Func{T1, T2}"/> generating a <see cref="RoleData"/> to fake the others role whenever another player changes role.
/// </summary>
/// <remarks>See <see cref="SetAppearance(Func{Player,RoleData})"/> for usage.</remarks>
public List<Func<Player, RoleData>> FakeRoleGenerator { get; } = ListPool<Func<Player, RoleData>>.Pool.Get();

/// <summary>
/// Gets the role that player had before changing role.
/// </summary>
Expand Down Expand Up @@ -1839,6 +1851,41 @@ public void TrySetCustomRoleFriendlyFire(string roleTypeId, Dictionary<RoleTypeI
/// <returns> Whether the item was able to be added. </returns>
public bool TryRemoveCustomeRoleFriendlyFire(string role) => CustomRoleFriendlyFireMultiplier.Remove(role);

/// <summary>
/// Adds a <see cref="Func{Player, RoleData}"/> from a <see cref="Player"/> to a <see cref="RoleTypeId"/> that is used every time a players role changes.
/// </summary>
/// <param name="generator">The function that determines if a players role will be faked (to a given viewer) after their role changes.</param>
/// <remarks>The first Func in <see cref="FakeRoleGenerator"/> that returns a RoleData that is not <see cref="RoleData.None"/> will be used for faking appearance.</remarks>
public void SetAppearance(Func<Player, RoleData> generator)
{
FakeRoleGenerator.Add(generator);
}

/// <summary>
/// Fakes this players role to other viewers.
/// </summary>
/// <param name="viewers">The players to affect.</param>
/// <param name="fakeRole">The fake role.</param>
/// /// <param name="unitId">The Unit ID of the player, if <paramref name="fakeRole"/> is an NTF role.</param>
public void SetAppearance(IEnumerable<Player> viewers, RoleTypeId fakeRole, byte unitId = 0)
{
foreach (Player player in viewers)
{
player.SetAppearance(this, fakeRole, unitId);
}
}

/// <summary>
/// Fakes another players role to this player.
/// </summary>
/// <param name="player">The target.</param>
/// <param name="fakeRole">The fake role.</param>
/// <param name="unitId">The Unit ID of the player, if <paramref name="fakeRole"/> is an NTF role.</param>
public void SetAppearance(Player player, RoleTypeId fakeRole, byte unitId = 0)
{
FakeRoles[player] = new RoleData(fakeRole, unitId);
}

/// <summary>
/// Forces the player's client to play the weapon reload animation, bypassing server-side checks.
/// </summary>
Expand Down
76 changes: 76 additions & 0 deletions EXILED/Exiled.API/Structs/RoleData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// -----------------------------------------------------------------------
// <copyright file="RoleData.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Structs
{
using System;

using PlayerRoles;

/// <summary>
/// A struct representing all data regarding a fake role.
/// </summary>
public struct RoleData : IEquatable<RoleData>
{
/// <summary>
/// Initializes a new instance of the <see cref="RoleData"/> struct.
/// </summary>
/// <param name="role">The fake role.</param>
/// <param name="unitId">The fake UnitID, if <paramref name="role"/> is an NTF role.</param>
public RoleData(RoleTypeId role = RoleTypeId.None, byte unitId = 0)
{
Role = role;
UnitId = unitId;
}

/// <summary>
/// Gets the static <see cref="RoleData"/> representing no data.
/// </summary>
public static RoleData None { get; } = new(RoleTypeId.None);

/// <summary>
/// Gets or sets the fake role.
/// </summary>
public RoleTypeId Role { get; set; }

/// <summary>
/// Gets or sets the UnitID of the fake role, if <see cref="Role"/> is an NTF role.
/// </summary>
public byte UnitId { get; set; }

/// <summary>
/// Checks if 2 <see cref="RoleData"/> are equal.
/// </summary>
/// <param name="left">A <see cref="RoleData"/>.</param>
/// <param name="right">The other <see cref="RoleData"/>.</param>
/// <returns>Whether the parameters are equal.</returns>
public static bool operator ==(RoleData left, RoleData right) => left.Equals(right);

/// <summary>
/// Checks if 2 <see cref="RoleData"/> are not equal.
/// </summary>
/// <param name="left">A <see cref="RoleData"/>.</param>
/// <param name="right">The other <see cref="RoleData"/>.</param>
/// <returns>Whether the parameters are not equal.</returns>
public static bool operator !=(RoleData left, RoleData right) => !left.Equals(right);

/// <inheritdoc/>
public bool Equals(RoleData other) => Role == other.Role && UnitId == other.UnitId;

/// <inheritdoc/>
public override bool Equals(object obj) => obj is RoleData other && Equals(other);

/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return ((int)Role * 397) ^ UnitId.GetHashCode();
}
}
}
}
8 changes: 7 additions & 1 deletion EXILED/Exiled.Events/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ namespace Exiled.Events
using HarmonyLib;
using InventorySystem.Items.Pickups;
using InventorySystem.Items.Usables;
using PlayerRoles.FirstPersonControl.NetworkMessages;
using PlayerRoles.Ragdolls;
using PlayerRoles.RoleAssign;

using Respawning;
using UnityEngine.SceneManagement;
using UserSettings.ServerSpecific;
Expand Down Expand Up @@ -68,6 +68,7 @@ public override void OnEnabled()
Handlers.Server.RestartingRound += Handlers.Internal.Round.OnRestartingRound;
Handlers.Server.RoundStarted += Handlers.Internal.Round.OnRoundStarted;
Handlers.Player.ChangingRole += Handlers.Internal.Round.OnChangingRole;
Handlers.Player.Spawned += Handlers.Internal.Round.OnSpawned;
Handlers.Player.SpawningRagdoll += Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified += Handlers.Internal.Round.OnVerified;
Expand All @@ -92,6 +93,8 @@ public override void OnEnabled()
LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon += Handlers.Player.OnReloadingWeapon;
LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon += Handlers.Player.OnUnloadingWeapon;

FpcServerPositionDistributor.RoleSyncEvent += Handlers.Internal.Round.OnRoleSyncEvent;

ServerConsole.ReloadServerName();
}

Expand All @@ -110,6 +113,7 @@ public override void OnDisabled()
Handlers.Server.RestartingRound -= Handlers.Internal.Round.OnRestartingRound;
Handlers.Server.RoundStarted -= Handlers.Internal.Round.OnRoundStarted;
Handlers.Player.ChangingRole -= Handlers.Internal.Round.OnChangingRole;
Handlers.Player.Spawned -= Handlers.Internal.Round.OnSpawned;
Handlers.Player.SpawningRagdoll -= Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense -= Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified -= Handlers.Internal.Round.OnVerified;
Expand All @@ -129,6 +133,8 @@ public override void OnDisabled()

LabApi.Events.Handlers.PlayerEvents.ReloadingWeapon -= Handlers.Player.OnReloadingWeapon;
LabApi.Events.Handlers.PlayerEvents.UnloadingWeapon -= Handlers.Player.OnUnloadingWeapon;

FpcServerPositionDistributor.RoleSyncEvent -= Handlers.Internal.Round.OnRoleSyncEvent;
}

/// <summary>
Expand Down
56 changes: 55 additions & 1 deletion EXILED/Exiled.Events/Handlers/Internal/Round.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Exiled.Events.Handlers.Internal
{
using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -28,6 +29,7 @@ namespace Exiled.Events.Handlers.Internal
using InventorySystem.Items.Firearms.Attachments.Components;
using InventorySystem.Items.Usables;
using InventorySystem.Items.Usables.Scp244.Hypothermia;
using Mirror;
using PlayerRoles;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.RoleAssign;
Expand Down Expand Up @@ -85,6 +87,22 @@ public static void OnChangingRole(ChangingRoleEventArgs ev)
ev.Player.Inventory.ServerDropEverything();
}

/// <inheritdoc cref="Handlers.Player.OnSpawned(SpawnedEventArgs)" />
public static void OnSpawned(SpawnedEventArgs ev)
{
foreach (Player viewer in Player.Enumerable.Except(new[] { ev.Player }))
{
foreach (Func<Player, RoleData> generator in viewer.FakeRoleGenerator)
{
RoleData data = generator(ev.Player);
if (data.Role != RoleTypeId.None)
{
viewer.FakeRoles[ev.Player] = data;
}
}
}
}

/// <inheritdoc cref="Handlers.Player.OnSpawningRagdoll(SpawningRagdollEventArgs)" />
public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev)
{
Expand Down Expand Up @@ -118,12 +136,48 @@ public static void OnVerified(VerifiedEventArgs ev)
}

// Fix bug that player that Join do not receive information about other players Scale
foreach (Player player in ReferenceHub.AllHubs.Select(Player.Get))
foreach (Player player in Player.Enumerable)
{
player.SetFakeScale(player.Scale, new List<Player>() { ev.Player });

if (player != ev.Player)
{
foreach (Func<Player, RoleData> generator in player.FakeRoleGenerator)
{
RoleData data = generator(ev.Player);
if (data.Role != RoleTypeId.None)
{
ev.Player.FakeRoles[player] = data;
}
}
}
}
}

/// <summary>
/// Makes fake role API work.
/// </summary>
/// <param name="viewerHub">The <see cref="ReferenceHub"/> of the viewer.</param>
/// <param name="ownerHub">The <see cref="ReferenceHub"/> of the player.</param>
/// <param name="actualRole">The actual <see cref="RoleTypeId"/>.</param>
/// <param name="writer">The pooled <see cref="NetworkWriter"/>.</param>
/// <returns>A role, fake if needed.</returns>
public static RoleTypeId OnRoleSyncEvent(ReferenceHub viewerHub, ReferenceHub ownerHub, RoleTypeId actualRole, NetworkWriter writer)
{
Player viewer = Player.Get(viewerHub);
Player owner = Player.Get(ownerHub);

if (viewer.FakeRoles.TryGetValue(owner, out RoleData data))
{
if (data.UnitId != 0)
writer.WriteByte(data.UnitId);

return data.Role;
}

return actualRole;
}

private static void GenerateAttachments()
{
foreach (FirearmType firearmType in EnumUtils<FirearmType>.Values)
Expand Down
Loading