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
56 changes: 56 additions & 0 deletions EXILED/Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,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 @@ -413,6 +414,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 @@ -613,6 +619,12 @@ internal set
}
}

/// <summary>
/// Gets a <see cref="List{T}"/> of <see cref="Func{T1, T2}"/> generating a <see cref="RoleData"/> to fake this players role whenever this 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 @@ -1911,6 +1923,50 @@ 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 this players role changes.
/// </summary>
/// <param name="generator">The function that determines if this players role will be faked (to a 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.
/// <para>An example use case would be to make a scientist appear as a Class-D to all other Class-D, that Func would look like:
/// <code>
/// player => player.Role.Team is Team.ClassD ? new RoleData(RoleTypeId.ClassD) : RoleData.None
/// </code>
/// This method can be further optimized by only using static RoleData instances in your Funcs.
/// </para>
/// </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="authority">How to handle edge cases.</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, RoleData.Authority authority = RoleData.Authority.None, byte unitId = 0)
{
foreach (Player player in viewers)
{
player.SetAppearance(this, fakeRole, authority, 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="authority">How to handle edge cases.</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, RoleData.Authority authority = RoleData.Authority.None, byte unitId = 0)
{
FakeRoles[player] = new RoleData(fakeRole, authority, unitId);
}

/// <summary>
/// Forces the player's client to play the weapon reload animation, bypassing server-side checks.
/// </summary>
Expand Down
124 changes: 124 additions & 0 deletions EXILED/Exiled.API/Structs/RoleData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// -----------------------------------------------------------------------
// <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 Mirror;
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="authority">The authority of the role data.</param>
/// <param name="unitId">The fake UnitID, if <paramref name="role"/> is an NTF role.</param>
/// <param name="customData">An action used to write custom data if this role data is used to send a fake role. For 99% of uses, just leave this as null.</param>
public RoleData(RoleTypeId role, Authority authority = Authority.None, byte unitId = 0, Action<NetworkWriter> customData = null)
{
Role = role;
DataAuthority = authority;
UnitId = unitId;
CustomData = customData;
}

/// <summary>
/// Represents flags for how Exiled should handle edge cases.
/// </summary>
[Flags]
public enum Authority
{
/// <summary>
/// Indicates Exiled should only fake the role of the target of this <see cref="RoleData"/> in ideal conditions.
/// </summary>
None = 0,

/// <summary>
/// Indicates that Exiled should attempt to override other plugins fake role attempts if they exist.
/// </summary>
/// <remarks>This is not guaranteed to always work.</remarks>
Override = 1,

/// <summary>
/// Indicates that the fake role should always be sent without checking if the player is dead, etc...
/// </summary>
Always = 2,

/// <summary>
/// Indicates that Exiled should not reset the fake role if the target of this <see cref="RoleData"/> dies.
/// </summary>
Persist = 4,

/// <summary>
/// Indicates that this <see cref="RoleData"/> can make a player view themselves as a different role.
/// </summary>
AffectSelf = 8,

/// <summary>
/// Indicates that this <see cref="RoleData"/> can affect dummies.
/// </summary>
AffectNPCs = 16,
}

/// <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>
/// Gets or sets the authority of this <see cref="RoleData"/> instance. see <see cref="Authority"/> for details.
/// </summary>
public Authority DataAuthority { get; set; } = Authority.None;

/// <summary>
/// Gets or sets custom data written to network writers when fake data is generated.
/// </summary>
/// <remarks>Leave this value as null unless you are writing custom role-specific data.</remarks>
public Action<NetworkWriter> CustomData { 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 && DataAuthority == other.DataAuthority && UnitId == other.UnitId;

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

/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine((int)Role, UnitId, (int)DataAuthority, CustomData);
}
}
9 changes: 9 additions & 0 deletions EXILED/Exiled.Events/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Exiled.Events
using HarmonyLib;
using InventorySystem.Items.Pickups;
using InventorySystem.Items.Usables;
using PlayerRoles.FirstPersonControl.NetworkMessages;
using PlayerRoles.Ragdolls;
using PlayerRoles.RoleAssign;

Expand Down Expand Up @@ -68,6 +69,8 @@ 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.Dying.Subscribe(Handlers.Internal.Round.OnDying, -100);
Handlers.Player.SpawningRagdoll += Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified += Handlers.Internal.Round.OnVerified;
Expand All @@ -93,6 +96,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;

LabApi.Events.Handlers.Scp127Events.Talking += Handlers.Scp127.OnTalking;
LabApi.Events.Handlers.Scp127Events.Talked += Handlers.Scp127.OnTalked;
LabApi.Events.Handlers.Scp127Events.GainingExperience += Handlers.Scp127.OnGainingExperience;
Expand All @@ -118,6 +123,8 @@ 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.Dying -= Handlers.Internal.Round.OnDying;
Handlers.Player.SpawningRagdoll -= Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense -= Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified -= Handlers.Internal.Round.OnVerified;
Expand All @@ -138,6 +145,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;

LabApi.Events.Handlers.Scp127Events.Talking -= Handlers.Scp127.OnTalking;
LabApi.Events.Handlers.Scp127Events.Talked -= Handlers.Scp127.OnTalked;
LabApi.Events.Handlers.Scp127Events.GainingExperience -= Handlers.Scp127.OnGainingExperience;
Expand Down
Loading
Loading