diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs
index e058efd079..698b74ad80 100644
--- a/EXILED/Exiled.API/Features/Npc.cs
+++ b/EXILED/Exiled.API/Features/Npc.cs
@@ -18,12 +18,14 @@ namespace Exiled.API.Features
using CommandSystem.Commands.RemoteAdmin.Dummies;
using Exiled.API.Enums;
using Exiled.API.Features.Components;
+ using Exiled.API.Features.CustomStats;
using Exiled.API.Features.Roles;
using Footprinting;
using GameCore;
using MEC;
using Mirror;
using PlayerRoles;
+ using PlayerStatsSystem;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -33,6 +35,11 @@ namespace Exiled.API.Features
///
public class Npc : Player
{
+ ///
+ /// The time it takes for the NPC to receive its , and .
+ ///
+ public const float SpawnSetRoleDelay = 0.5f;
+
///
public Npc(ReferenceHub referenceHub)
: base(referenceHub)
@@ -252,10 +259,13 @@ public static Npc Spawn(string name, RoleTypeId role, Vector3 position)
{
Npc npc = new(DummyUtils.SpawnDummy(name));
- Timing.CallDelayed(0.5f, () =>
+ Timing.CallDelayed(SpawnSetRoleDelay, () =>
{
- npc.Role.Set(role);
+ npc.Role.Set(role, SpawnReason.ForceClass);
npc.Position = position;
+ npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = npc.CustomHealthStat = new CustomHealthStat { Hub = npc.ReferenceHub };
+ npc.Health = npc.MaxHealth; // otherwise the npc will spawn with 0 health
+ npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = npc.CustomHumeShieldStat = new CustomHumeShieldStat { Hub = npc.ReferenceHub };
});
Dictionary.Add(npc.GameObject, npc);
@@ -274,9 +284,12 @@ public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ign
{
Npc npc = new(DummyUtils.SpawnDummy(name));
- Timing.CallDelayed(0.5f, () =>
+ Timing.CallDelayed(SpawnSetRoleDelay, () =>
{
npc.Role.Set(role, SpawnReason.ForceClass, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory);
+ npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HealthStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = npc.CustomHealthStat = new CustomHealthStat { Hub = npc.ReferenceHub };
+ npc.Health = npc.MaxHealth; // otherwise the npc will spawn with 0 health
+ npc.ReferenceHub.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = npc.ReferenceHub.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = npc.CustomHumeShieldStat = new CustomHumeShieldStat { Hub = npc.ReferenceHub };
if (position is not null)
npc.Position = position.Value;
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index b25d277f45..45682414d0 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -56,7 +56,6 @@ namespace Exiled.API.Features
using PluginAPI.Core;
using RelativePositioning;
using RemoteAdmin;
- using Respawning.NamingRules;
using RoundRestarting;
using UnityEngine;
using Utils;
@@ -96,8 +95,6 @@ public class Player : TypeCastObject, IEntity, IWorldSpace
private readonly HashSet componentsInChildren = new();
private ReferenceHub referenceHub;
- private CustomHealthStat healthStat;
- private CustomHumeShieldStat humeShieldStat;
private Role role;
///
@@ -145,6 +142,11 @@ public Player(GameObject gameObject)
///
public static Dictionary UserIdsCache { get; } = new(20);
+ ///
+ /// Gets or sets a .
+ ///
+ public CustomHumeShieldStat CustomHumeShieldStat { get; protected set; }
+
///
public IReadOnlyCollection ComponentsInChildren => componentsInChildren;
@@ -178,8 +180,8 @@ private set
Inventory = value.inventory;
CameraTransform = value.PlayerCameraReference;
- value.playerStats._dictionarizedTypes[typeof(HealthStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = healthStat = new CustomHealthStat { Hub = value };
- value.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = humeShieldStat = new CustomHumeShieldStat { Hub = value };
+ value.playerStats._dictionarizedTypes[typeof(HealthStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HealthStat))] = CustomHealthStat = new CustomHealthStat { Hub = value };
+ value.playerStats._dictionarizedTypes[typeof(HumeShieldStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(HumeShieldStat))] = CustomHumeShieldStat = new CustomHumeShieldStat { Hub = value };
}
}
@@ -867,13 +869,13 @@ public DangerStackBase[] Dangers
///
public float Health
{
- get => healthStat.CurValue;
+ get => CustomHealthStat.CurValue;
set
{
if (value > MaxHealth)
MaxHealth = value;
- healthStat.CurValue = value;
+ CustomHealthStat.CurValue = value;
}
}
@@ -882,8 +884,8 @@ public float Health
///
public float MaxHealth
{
- get => healthStat.MaxValue;
- set => healthStat.CustomMaxValue = value;
+ get => CustomHealthStat.MaxValue;
+ set => CustomHealthStat.CustomMaxValue = value;
}
///
@@ -929,8 +931,8 @@ public float MaxArtificialHealth
/// This value can bypass the role's hume shield maximum. However, this value will only be visible to the end-player as Hume Shield if is . Otherwise, the game will treat the player as though they have the amount of Hume Shield specified, even though they cannot see it.
public float HumeShield
{
- get => HumeShieldStat.CurValue;
- set => HumeShieldStat.CurValue = value;
+ get => CustomHumeShieldStat.CurValue;
+ set => CustomHumeShieldStat.CurValue = value;
}
///
@@ -938,8 +940,8 @@ public float HumeShield
///
public float MaxHumeShield
{
- get => humeShieldStat.MaxValue;
- set => humeShieldStat.CustomMaxValue = value;
+ get => CustomHumeShieldStat.MaxValue;
+ set => CustomHumeShieldStat.CustomMaxValue = value;
}
///
@@ -947,8 +949,8 @@ public float MaxHumeShield
///
public float HumeShieldRegenerationMultiplier
{
- get => humeShieldStat.ShieldRegenerationMultiplier;
- set => humeShieldStat.ShieldRegenerationMultiplier = value;
+ get => CustomHumeShieldStat.ShieldRegenerationMultiplier;
+ set => CustomHumeShieldStat.ShieldRegenerationMultiplier = value;
}
///
@@ -958,9 +960,9 @@ public float HumeShieldRegenerationMultiplier
///
/// Gets the player's .
- /// TODO: Change to .
///
- public HumeShieldStat HumeShieldStat => humeShieldStat;
+ [Obsolete("Use " + nameof(CustomHumeShieldStat) + " instead.")]
+ public HumeShieldStat HumeShieldStat => CustomHumeShieldStat;
///
/// Gets or sets the item in the player's hand. Value will be if the player is not holding anything.
@@ -1184,6 +1186,11 @@ public bool IsSpawnProtected
///
internal static ConditionalWeakTable UnverifiedPlayers { get; } = new();
+ ///
+ /// Gets or sets a .
+ ///
+ protected CustomHealthStat CustomHealthStat { get; set; }
+
///
/// Converts NwPluginAPI player to EXILED player.
///
diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs
index 7d7fd8b5bf..60d3f44d60 100644
--- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs
+++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs
@@ -24,9 +24,7 @@ namespace Exiled.CustomRoles.API.Features
using Exiled.Events.EventArgs.Player;
using Exiled.Loader;
using InventorySystem.Configs;
-
using MEC;
-
using PlayerRoles;
using UnityEngine;
@@ -38,6 +36,8 @@ namespace Exiled.CustomRoles.API.Features
///
public abstract class CustomRole
{
+ private const float AddRoleDelay = 0.25f;
+
private static Dictionary typeLookupTable = new();
private static Dictionary stringLookupTable = new();
@@ -499,31 +499,31 @@ public virtual void Destroy()
public virtual void AddRole(Player player)
{
Log.Debug($"{Name}: Adding role to {player.Nickname}.");
- TrackedPlayers.Add(player);
+ player.UniqueRole = Name;
if (Role != RoleTypeId.None)
{
- switch (KeepPositionOnSpawn)
+ if (KeepPositionOnSpawn)
{
- case true when KeepInventoryOnSpawn:
+ if (KeepInventoryOnSpawn)
player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None);
- break;
- case true:
+ else
player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory);
- break;
- default:
- {
- if (KeepInventoryOnSpawn && player.IsAlive)
- player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint);
- else
- player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All);
- break;
- }
+ }
+ else
+ {
+ if (KeepInventoryOnSpawn && player.IsAlive)
+ player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint);
+ else
+ player.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.All);
}
}
+ player.UniqueRole = Name;
+ TrackedPlayers.Add(player);
+
Timing.CallDelayed(
- 0.25f,
+ AddRoleDelay,
() =>
{
if (!KeepInventoryOnSpawn)
@@ -574,7 +574,6 @@ public virtual void AddRole(Player player)
ShowMessage(player);
ShowBroadcast(player);
RoleAdded(player);
- player.UniqueRole = Name;
player.TryAddCustomRoleFriendlyFire(Name, CustomRoleFFMultiplier);
if (!string.IsNullOrEmpty(ConsoleMessage))
@@ -912,10 +911,8 @@ protected virtual void RoleRemoved(Player player)
private void OnInternalChangingNickname(ChangingNicknameEventArgs ev)
{
- if (!Check(ev.Player))
- return;
-
- ev.Player.CustomInfo = $"{ev.NewName}\n{CustomInfo}";
+ if (Check(ev.Player))
+ ev.Player.CustomInfo = $"{ev.NewName}\n{CustomInfo}";
}
private void OnInternalSpawned(SpawnedEventArgs ev)
@@ -926,13 +923,8 @@ private void OnInternalSpawned(SpawnedEventArgs ev)
private void OnInternalChangingRole(ChangingRoleEventArgs ev)
{
- if(ev.Reason == SpawnReason.Destroyed)
- return;
-
- if (Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && ev.NewRole != Role && !KeepRoleOnChangingRole)))
- {
+ if (ev.IsAllowed && ev.Reason != SpawnReason.Destroyed && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole)))
RemoveRole(ev.Player);
- }
}
private void OnSpawningRagdoll(SpawningRagdollEventArgs ev)
diff --git a/EXILED/Exiled.CustomRoles/Commands/Give.cs b/EXILED/Exiled.CustomRoles/Commands/Give.cs
index 4eb357feb9..2f74b46498 100644
--- a/EXILED/Exiled.CustomRoles/Commands/Give.cs
+++ b/EXILED/Exiled.CustomRoles/Commands/Give.cs
@@ -19,7 +19,6 @@ namespace Exiled.CustomRoles.Commands
using Exiled.Permissions.Extensions;
using RemoteAdmin;
- using Utils;
///
/// The command to give a role to player(s).
@@ -108,9 +107,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s
}
foreach (Player player in list)
- {
role.AddRole(player);
- }
response = $"Customrole {role.Name} given to {list.Count()} players!";
diff --git a/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs b/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
index 0884e28710..8fc08e1b98 100644
--- a/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
@@ -16,7 +16,6 @@ namespace Exiled.Events.Patches.Events.Map
using Exiled.API.Extensions;
using Exiled.Events.EventArgs.Map;
using Exiled.Events.Patches.Generic;
- using Footprinting;
using HarmonyLib;
using InventorySystem.Items.ThrowableProjectiles;
using UnityEngine;
@@ -67,12 +66,15 @@ private static void ProcessEvent(FlashbangGrenade instance, float distance)
HashSet targetToAffect = HashSetPool.Pool.Get();
foreach (Player player in ReferenceHub.AllHubs.Select(Player.Get))
{
- if ((instance.transform.position - player.Position).sqrMagnitude >= distance)
+ if ((instance.transform.position - player.Position).sqrMagnitude > distance)
continue;
+
if (!ExiledEvents.Instance.Config.CanFlashbangsAffectThrower && instance.PreviousOwner.CompareLife(player.ReferenceHub))
continue;
+
if (!IndividualFriendlyFire.CheckFriendlyFirePlayer(instance.PreviousOwner, player.ReferenceHub) && !instance.PreviousOwner.CompareLife(player.ReferenceHub))
continue;
+
if (Physics.Linecast(instance.transform.position, player.CameraTransform.position, instance._blindingMask))
continue;