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;