Skip to content

Commit

Permalink
feat: PR-133-FakeAppearance (#402)
Browse files Browse the repository at this point in the history
* PR-133-FakeAppearance

* SendingRole Event
  • Loading branch information
louis1706 authored Jan 21, 2025
1 parent 8cb5ead commit cd1074e
Show file tree
Hide file tree
Showing 13 changed files with 581 additions and 64 deletions.
69 changes: 11 additions & 58 deletions EXILED/Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ namespace Exiled.API.Extensions
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

using Exiled.API.Enums;
using Exiled.API.Features.Roles;

using Features;
using Features.Pools;

Expand All @@ -24,7 +27,6 @@ namespace Exiled.API.Extensions
using PlayerRoles;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp049.Zombies;
using PlayerRoles.PlayableScps.Scp1507;
using PlayerRoles.Voice;
using RelativePositioning;

Expand Down Expand Up @@ -229,9 +231,7 @@ public static void SetName(this Player target, Player player, string name)
/// </summary>
/// <param name="player">Player to change.</param>
/// <param name="type">Model type.</param>
/// <param name="skipJump">Whether to skip the little jump that works around an invisibility issue.</param>
/// <param name="unitId">The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).</param>
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);
public static void ChangeAppearance(this Player player, RoleTypeId type) => ChangeAppearance(player, type, Player.List.Where(x => x != player));

/// <summary>
/// Change <see cref="Player"/> character model for appearance.
Expand All @@ -240,70 +240,23 @@ public static void SetName(this Player target, Player player, string name)
/// <param name="player">Player to change.</param>
/// <param name="type">Model type.</param>
/// <param name="playersToAffect">The players who should see the changed appearance.</param>
/// <param name="skipJump">Whether to skip the little jump that works around an invisibility issue.</param>
/// <param name="unitId">The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF).</param>
public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumerable<Player> playersToAffect, bool skipJump = false, byte unitId = 0)
public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumerable<Player> playersToAffect)
{
if (!player.IsConnected || !RoleExtensions.TryGetRoleBase(type, out PlayerRoleBase roleBase))
if (!player.IsConnected)
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 ZombieRole)
{
if (player.Role.Base is not ZombieRole)
isRisky = true;

writer.WriteUShort((ushort)Mathf.Clamp(Mathf.CeilToInt(player.MaxHealth), ushort.MinValue, ushort.MaxValue));
writer.WriteBool(true);
}

if (roleBase is Scp1507Role)
if (!player.Role.CheckAppearanceCompatibility(type))
{
if (player.Role.Base is not Scp1507Role)
isRisky = true;

writer.WriteByte((byte)player.Role.SpawnReason);
}

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);
Log.Error($"Prevent Seld-Desync of {player.Nickname} ({player.Role.Type}) with {type}");
return;
}

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}");
player.Role.TrySetIndividualAppearance(target, type, false);
}

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;
player.Role.UpdateAppearance();
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion EXILED/Exiled.API/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public static void CopyProperties(this object target, object source)
throw new InvalidTypeException("Target and source type mismatch!");

foreach (PropertyInfo sourceProperty in type.GetProperties())
type.GetProperty(sourceProperty.Name)?.SetValue(target, sourceProperty.GetValue(source, null), null);
{
if (sourceProperty.SetMethod != null && sourceProperty.GetMethod != null)
sourceProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
}
}
}
}
31 changes: 30 additions & 1 deletion EXILED/Exiled.API/Extensions/RoleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ namespace Exiled.API.Extensions
using System.Linq;

using Enums;
using Features.Spawn;

using Exiled.API.Features;
using Exiled.API.Features.Roles;
using Exiled.API.Features.Spawn;
using Footprinting;
using InventorySystem;
using InventorySystem.Configs;
Expand Down Expand Up @@ -229,6 +232,32 @@ public static Dictionary<AmmoType, ushort> GetStartingAmmo(this RoleTypeId roleT
return info.Ammo.ToDictionary(kvp => kvp.Key.GetAmmoType(), kvp => kvp.Value);
}

/// <summary>
/// Gets a custom appearance for target <see cref="Player"/>, using <see cref="Role.GlobalAppearance"/>, <see cref="Role.TeamAppearances"/> and <see cref="Role.IndividualAppearances"/>.
/// </summary>
/// <param name="role">The player ><see cref="Role"/>, whose appearance we want to get.</param>
/// <param name="player">Target <see cref="Player"/>.</param>
/// <returns>A valid <see cref="RoleTypeId"/>, what target <see cref="Player"/> will see.</returns>
public static RoleTypeId GetAppearanceForPlayer(this Role role, Player player)
{
RoleTypeId appearance = role.GlobalAppearance;

if (player == null)
return appearance;

if (role.IndividualAppearances.TryGetValue(player, out appearance))
{
return appearance;
}

if (role.TeamAppearances.TryGetValue(player.Role.Team, out appearance))
{
return appearance;
}

return role.GlobalAppearance;
}

/// <summary>
/// Gets the <see cref="SpawnableFaction"/> of a <see cref="SpawnableWaveBase"/>.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions EXILED/Exiled.API/Features/Roles/FpcRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ namespace Exiled.API.Features.Roles
using System.Collections.Generic;
using System.Reflection;

using Exiled.API.Extensions;
using Exiled.API.Features.Pools;
using HarmonyLib;

using Mirror;

using PlayerRoles;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.Ragdolls;
Expand Down Expand Up @@ -282,5 +286,23 @@ public void ResetStamina(bool multipliers = false)
StaminaUsageMultiplier = 1f;
StaminaRegenMultiplier = 1f;
}

/// <inheritdoc/>
internal override bool CheckAppearanceCompatibility(RoleTypeId newAppearance)
{
if (!RoleExtensions.TryGetRoleBase(newAppearance, out PlayerRoleBase roleBase))
return false;

return roleBase is FpcStandardRoleBase;
}

/// <inheritdoc/>
internal override void SendAppearanceSpawnMessage(NetworkWriter writer, PlayerRoleBase basicRole)
{
FpcStandardRoleBase fpcRole = (FpcStandardRoleBase)basicRole;
fpcRole.FpcModule.MouseLook.GetSyncValues(0, out ushort syncH, out ushort _);
writer.WriteRelativePosition(new RelativePosition(fpcRole._hubTransform.position));
writer.WriteUShort(syncH);
}
}
}
11 changes: 11 additions & 0 deletions EXILED/Exiled.API/Features/Roles/HumanRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Exiled.API.Features.Roles
{
using Mirror;

using PlayerRoles;

using Respawning;
Expand Down Expand Up @@ -62,5 +64,14 @@ public byte UnitNameId
/// <param name="hitbox">The <see cref="HitboxType"/>.</param>
/// <returns>The armor efficacy.</returns>
public int GetArmorEfficacy(HitboxType hitbox) => Base.GetArmorEfficacy(hitbox);

/// <inheritdoc/>
internal override void SendAppearanceSpawnMessage(NetworkWriter writer, PlayerRoleBase basicRole)
{
if (UsesUnitNames)
writer.WriteByte(basicRole is HumanGameRole humanRole && humanRole.UsesUnitNames ? humanRole.UnitNameId : (byte)0);

base.SendAppearanceSpawnMessage(writer, basicRole);
}
}
}
Loading

0 comments on commit cd1074e

Please sign in to comment.