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