diff --git a/EXILED/Exiled.API/Features/CustomHealthStat.cs b/EXILED/Exiled.API/Features/CustomStats/CustomHealthStat.cs similarity index 95% rename from EXILED/Exiled.API/Features/CustomHealthStat.cs rename to EXILED/Exiled.API/Features/CustomStats/CustomHealthStat.cs index af351bf7a..f0d71fc66 100644 --- a/EXILED/Exiled.API/Features/CustomHealthStat.cs +++ b/EXILED/Exiled.API/Features/CustomStats/CustomHealthStat.cs @@ -5,7 +5,7 @@ // // ----------------------------------------------------------------------- -namespace Exiled.API.Features +namespace Exiled.API.Features.CustomStats { using PlayerStatsSystem; diff --git a/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs b/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs new file mode 100644 index 000000000..78c4cd807 --- /dev/null +++ b/EXILED/Exiled.API/Features/CustomStats/CustomHumeShieldStat.cs @@ -0,0 +1,79 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.CustomStats +{ + using Mirror; + using PlayerRoles.PlayableScps.HumeShield; + using PlayerStatsSystem; + using UnityEngine; + using Utils.Networking; + + /// + /// A custom version of which allows the player's max amount of HumeShield to be changed. + /// + public class CustomHumeShieldStat : HumeShieldStat + { + /// + public override float MaxValue => CustomMaxValue == -1 ? base.MaxValue : CustomMaxValue; + + /// + /// Gets or sets the multiplier for gaining HumeShield. + /// + public float ShieldRegenerationMultiplier { get; set; } = 1; + + /// + /// Gets or sets the maximum amount of HumeShield the player can have. + /// + public float CustomMaxValue { get; set; } = -1; + + private float ShieldRegeneration => TryGetHsModule(out HumeShieldModuleBase controller) ? controller.HsRegeneration * ShieldRegenerationMultiplier : 0; + + /// + public override void Update() + { + if (MaxValue == -1 && ShieldRegenerationMultiplier is 1) + { + base.Update(); + return; + } + + if (!NetworkServer.active) + return; + + if (_valueDirty) + { + new SyncedStatMessages.StatMessage() + { + Stat = this, + SyncedValue = CurValue, + }.SendToHubsConditionally(CanReceive); + _lastSent = CurValue; + _valueDirty = false; + } + + if (ShieldRegeneration == 0) + return; + + float delta = ShieldRegeneration * Time.deltaTime; + + if (delta > 0) + { + if (CurValue >= MaxValue) + return; + + CurValue = Mathf.MoveTowards(CurValue, MaxValue, delta); + return; + } + + if (CurValue <= 0) + return; + + CurValue += delta; + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/CustomStats/CustomStaminaStat.cs b/EXILED/Exiled.API/Features/CustomStats/CustomStaminaStat.cs new file mode 100644 index 000000000..fde7149c2 --- /dev/null +++ b/EXILED/Exiled.API/Features/CustomStats/CustomStaminaStat.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.CustomStats +{ + using Mirror; + using PlayerStatsSystem; + using UnityEngine; + + /// + /// A custom version of which allows the player's maximum amount of Stamina to be changed. + /// + public class CustomStaminaStat : StaminaStat + { + /// + public override float MaxValue => CustomMaxValue == -1 ? base.MaxValue : CustomMaxValue; + + /// + /// Gets or sets the maximum amount of stamina the player will have. + /// + public float CustomMaxValue { get; set; } = -1; + + /// + /// Clamps a float to fit the current stamina bar. + /// + /// The value to clamp. + /// The clamped num. + public float Clamp(float value) => CustomMaxValue == -1 ? Mathf.Clamp01(value) : Mathf.Clamp(value, 0, MaxValue); + + /// + /// Overiding NW Method to sync Player percentage of Stamina. + /// + /// The writer. + public override void WriteValue(NetworkWriter writer) + { + if (CustomMaxValue == -1) + { + base.WriteValue(writer); + return; + } + + writer.WriteByte(ToByte(CurValue / CustomMaxValue)); + } + + /// + /// Overriding NW Method to sync Player percentage of Stamina. + /// + /// The reader. + /// the float value sync to server. + public override float ReadValue(NetworkReader reader) => CustomMaxValue == -1 ? base.ReadValue(reader) : CurValue * CustomMaxValue; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index be25a2823..1e6c7f255 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -19,6 +19,7 @@ namespace Exiled.API.Features using DamageHandlers; using Enums; using Exiled.API.Features.Core.Interfaces; + using Exiled.API.Features.CustomStats; using Exiled.API.Features.Doors; using Exiled.API.Features.Hazards; using Exiled.API.Features.Items; @@ -95,7 +96,9 @@ public class Player : TypeCastObject, IEntity, IWorldSpace private readonly HashSet componentsInChildren = new(); private ReferenceHub referenceHub; + private CustomHumeShieldStat humeShieldStat; private CustomHealthStat healthStat; + private CustomStaminaStat staminaStat; private Role role; /// @@ -177,6 +180,8 @@ private set 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(StaminaStat)] = value.playerStats.StatModules[Array.IndexOf(PlayerStats.DefinedModules, typeof(StaminaStat))] = staminaStat = new CustomStaminaStat { Hub = value }; } } @@ -930,6 +935,24 @@ public float HumeShield set => HumeShieldStat.CurValue = value; } + /// + /// Gets or sets the players maximum Hume Shield. + /// + public float MaxHumeShield + { + get => humeShieldStat.MaxValue; + set => humeShieldStat.CustomMaxValue = value; + } + + /// + /// Gets or sets the players multiplier for gaining HumeShield. + /// + public float HumeShieldRegenerationMultiplier + { + get => humeShieldStat.ShieldRegenerationMultiplier; + set => humeShieldStat.ShieldRegenerationMultiplier = value; + } + /// /// Gets a of all active Artificial Health processes on the player. /// @@ -938,7 +961,7 @@ public float HumeShield /// /// Gets the player's . /// - public HumeShieldStat HumeShieldStat => ReferenceHub.playerStats.GetModule(); + public HumeShieldStat HumeShieldStat => humeShieldStat; /// /// Gets or sets the item in the player's hand. Value will be if the player is not holding anything. @@ -970,7 +993,7 @@ public Item CurrentItem /// /// Gets the class. /// - public StaminaStat StaminaStat => ReferenceHub.playerStats.GetModule(); + public StaminaStat StaminaStat => staminaStat; /// /// Gets or sets the amount of stamina the player has. @@ -982,6 +1005,15 @@ public float Stamina set => StaminaStat.CurValue = value; } + /// + /// Gets or sets the players maximum stamina. + /// + public float MaxStamina + { + get => staminaStat.MaxValue; + set => staminaStat.CustomMaxValue = value; + } + /// /// Gets a value indicating whether the staff bypass is enabled. /// diff --git a/EXILED/Exiled.Events/Patches/Generic/FixStaminaStat.cs b/EXILED/Exiled.Events/Patches/Generic/FixStaminaStat.cs new file mode 100644 index 000000000..b87f17168 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/FixStaminaStat.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features.CustomStats; + using Exiled.API.Features.Pools; + using HarmonyLib; + using PlayerRoles.FirstPersonControl; + using PlayerStatsSystem; + + using static HarmonyLib.AccessTools; + + /// + /// Fix for . + /// + [HarmonyPatch(typeof(FpcStateProcessor), nameof(FpcStateProcessor.UpdateMovementState))] + internal static class FixStaminaStat + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldc_R4); + newInstructions.RemoveAt(index); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(FpcStateProcessor), nameof(FpcStateProcessor._stat))), + new(OpCodes.Callvirt, PropertyGetter(typeof(StaminaStat), nameof(StaminaStat.MaxValue))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file