using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using Agents; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using GTFO.API; using HarmonyLib; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using Player; using SNetwork; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("SharedStamina")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.2.0")] [assembly: AssemblyInformationalVersion("0.1.2")] [assembly: AssemblyProduct("SharedStamina")] [assembly: AssemblyTitle("SharedStamina")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SharedStamina { internal readonly struct ContributionData { public long Epoch { get; } public double Cumulative { get; } public ContributionData(long epoch, double cumulative) { Epoch = epoch; Cumulative = cumulative; } } internal readonly struct AckData { public ulong PlayerId { get; } public double Cumulative { get; } public AckData(ulong playerId, double cumulative) { PlayerId = playerId; Cumulative = cumulative; } } internal sealed class StateData { public long Epoch { get; set; } public bool Active { get; set; } public double Pool { get; set; } public double Capacity { get; set; } public int ParticipantCount { get; set; } public int HumanPlayers { get; set; } public int ModdedPlayers { get; set; } public List Acks { get; } = new List(); } internal static class PacketCodec { public static byte[] EncodeHello() { using MemoryStream memoryStream = new MemoryStream(8); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(2); return memoryStream.ToArray(); } public static bool TryDecodeHello(byte[] bytes, out int protocol) { protocol = 0; try { using BinaryReader binaryReader = CreateReader(bytes); protocol = binaryReader.ReadInt32(); return binaryReader.BaseStream.Position == binaryReader.BaseStream.Length; } catch { return false; } } public static byte[] EncodeContribution(long epoch, double cumulative) { using MemoryStream memoryStream = new MemoryStream(24); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(epoch); binaryWriter.Write(cumulative); return memoryStream.ToArray(); } public static bool TryDecodeContribution(byte[] bytes, out ContributionData data) { data = default(ContributionData); try { using BinaryReader binaryReader = CreateReader(bytes); data = new ContributionData(binaryReader.ReadInt64(), binaryReader.ReadDouble()); return binaryReader.BaseStream.Position == binaryReader.BaseStream.Length && !double.IsNaN(data.Cumulative) && !double.IsInfinity(data.Cumulative); } catch { return false; } } public static byte[] EncodeState(StateData state) { using MemoryStream memoryStream = new MemoryStream(96); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(state.Epoch); binaryWriter.Write(state.Active); binaryWriter.Write(state.Pool); binaryWriter.Write(state.Capacity); binaryWriter.Write(state.ParticipantCount); binaryWriter.Write(state.HumanPlayers); binaryWriter.Write(state.ModdedPlayers); binaryWriter.Write(state.Acks.Count); foreach (AckData ack in state.Acks) { binaryWriter.Write(ack.PlayerId); binaryWriter.Write(ack.Cumulative); } return memoryStream.ToArray(); } public static bool TryDecodeState(byte[] bytes, out StateData state) { state = new StateData(); try { using BinaryReader binaryReader = CreateReader(bytes); state.Epoch = binaryReader.ReadInt64(); state.Active = binaryReader.ReadBoolean(); state.Pool = binaryReader.ReadDouble(); state.Capacity = binaryReader.ReadDouble(); state.ParticipantCount = binaryReader.ReadInt32(); state.HumanPlayers = binaryReader.ReadInt32(); state.ModdedPlayers = binaryReader.ReadInt32(); int num = binaryReader.ReadInt32(); if (num < 0 || num > 4) { return false; } for (int i = 0; i < num; i++) { state.Acks.Add(new AckData(binaryReader.ReadUInt64(), binaryReader.ReadDouble())); } return binaryReader.BaseStream.Position == binaryReader.BaseStream.Length && state.Capacity > 0.0 && !double.IsNaN(state.Pool) && !double.IsInfinity(state.Pool); } catch { return false; } } private static BinaryReader CreateReader(byte[] bytes) { return new BinaryReader(new MemoryStream(bytes, writable: false)); } } [BepInPlugin("qlsl.gtfo.sharedstamina", "Shared Stamina", "0.1.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BasePlugin { public const string PluginGuid = "qlsl.gtfo.sharedstamina"; public const string PluginName = "Shared Stamina"; public const string PluginVersion = "0.1.2"; internal static ManualLogSource Logger { get; private set; } internal static ConfigEntry NetworkRate { get; private set; } internal static ConfigEntry ClientTimeout { get; private set; } public override void Load() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown Logger = ((BasePlugin)this).Log; NetworkRate = ((BasePlugin)this).Config.Bind("Network", "UpdatesPerSecond", 20, new ConfigDescription("Shared stamina network update rate.", (AcceptableValueBase)(object)new AcceptableValueRange(5, 30), Array.Empty())); ClientTimeout = ((BasePlugin)this).Config.Bind("Network", "ClientTimeoutSeconds", 3f, new ConfigDescription("How long the host waits before treating a modded client as missing.", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 10f), Array.Empty())); SharedStaminaController.Initialize(); ((BasePlugin)this).AddComponent(); new Harmony("qlsl.gtfo.sharedstamina").PatchAll(); ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(90, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("Shared Stamina"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("0.1.2"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded. Dynamic 1-4 player pool enabled; all participating humans must install the mod."); } log.LogInfo(val); } } public sealed class SharedStaminaBehaviour : MonoBehaviour { public void Update() { SharedStaminaController.Update(); } } internal static class SharedStaminaController { private enum PacketKind { Hello, Contribution, State } private readonly struct InboundPacket { public PacketKind Kind { get; } public ulong Sender { get; } public byte[] Bytes { get; } public InboundPacket(PacketKind kind, ulong sender, byte[] bytes) { Kind = kind; Sender = sender; Bytes = bytes; } } private readonly struct ClientPresence { public int Protocol { get; } public float LastSeen { get; } public ClientPresence(int protocol, float lastSeen) { Protocol = protocol; LastSeen = lastSeen; } } internal const int ProtocolVersion = 2; private const string HelloEvent = "qlsl.sharedstamina.hello.v2"; private const string ContributionEvent = "qlsl.sharedstamina.contribution.v2"; private const string StateEvent = "qlsl.sharedstamina.state.v2"; private static readonly ConcurrentQueue InboundPackets = new ConcurrentQueue(); private static readonly Dictionary ClientPresenceById = new Dictionary(); private static readonly Dictionary HostCumulativeById = new Dictionary(); private static bool _initialized; private static bool _sessionPresent; private static bool _hostActive; private static bool _sharedActive; private static long _epoch; private static double _serverPool = 1.0; private static double _capacity = 1.0; private static double _predictedPool = 1.0; private static double _localCumulative; private static double _localAcknowledged; private static float _lastAppliedRatio = float.NaN; private static float _nextHelloTime; private static float _nextNetworkTime; private static int _humanPlayers; private static int _moddedPlayers; private static int _lastLoggedHumanPlayers = -1; private static int _lastLoggedModdedPlayers = -1; private static bool _loggedStaminaHook; internal static void Initialize() { if (!_initialized) { NetworkAPI.RegisterFreeSizedEvent("qlsl.sharedstamina.hello.v2", (Action)delegate(ulong sender, byte[] bytes) { InboundPackets.Enqueue(new InboundPacket(PacketKind.Hello, sender, bytes)); }); NetworkAPI.RegisterFreeSizedEvent("qlsl.sharedstamina.contribution.v2", (Action)delegate(ulong sender, byte[] bytes) { InboundPackets.Enqueue(new InboundPacket(PacketKind.Contribution, sender, bytes)); }); NetworkAPI.RegisterFreeSizedEvent("qlsl.sharedstamina.state.v2", (Action)delegate(ulong sender, byte[] bytes) { InboundPackets.Enqueue(new InboundPacket(PacketKind.State, sender, bytes)); }); LevelAPI.OnEnterLevel += ResetForLevel; LevelAPI.OnLevelCleanup += ResetAll; _initialized = true; } } internal static void Update() { float realtimeSinceStartup = Time.realtimeSinceStartup; bool flag = HasPlayableLocalPlayer(); if (flag && !_sessionPresent) { ResetForLevel(); } else if (!flag && _sessionPresent) { ResetAll(); } _sessionPresent = flag; ProcessInbound(realtimeSinceStartup); if (!flag || !SNet.HasLocalPlayer) { return; } if (realtimeSinceStartup >= _nextHelloTime) { _nextHelloTime = realtimeSinceStartup + 1f; SendHello(realtimeSinceStartup); } if (SNet.IsMaster) { UpdateHostReadiness(realtimeSinceStartup); } float num = 1f / (float)Plugin.NetworkRate.Value; if (!(realtimeSinceStartup < _nextNetworkTime)) { _nextNetworkTime = realtimeSinceStartup + num; if (_sharedActive) { SendContribution(); } if (SNet.IsMaster) { BroadcastHostState(); } } } internal static void AfterLocalStaminaUpdate(PlayerStamina stamina) { //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown if (!_sharedActive || !_sessionPresent || (Object)(object)stamina == (Object)null || (Object)(object)stamina.m_owner == (Object)null || !((Agent)stamina.m_owner).IsLocallyOwned) { _lastAppliedRatio = float.NaN; return; } float maxStamina = PlayerStamina.MaxStamina; if (maxStamina <= 0f || _capacity <= 0.0) { return; } float num = Mathf.Clamp01(stamina.StaminaRelative); if (float.IsNaN(_lastAppliedRatio)) { _lastAppliedRatio = num; _predictedPool = ClampPool(_serverPool + (_localCumulative - _localAcknowledged)); } else { float num2 = num - _lastAppliedRatio; if (Math.Abs(num2) <= 0.5f) { _localCumulative += num2; _predictedPool = ClampPool(_predictedPool + (double)num2); } } float num3 = (float)Math.Clamp(_predictedPool / _capacity, 0.0, 1.0); stamina.Stamina = num3 * maxStamina; _lastAppliedRatio = num3; if (!_loggedStaminaHook) { _loggedStaminaHook = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(46, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Local stamina hook active: capacity=x"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_capacity, "0"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", ratio="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num3, "0.000"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logger.LogInfo(val); } } private static void ProcessInbound(float now) { InboundPacket result; while (InboundPackets.TryDequeue(out result)) { switch (result.Kind) { case PacketKind.Hello: ProcessHello(result.Sender, result.Bytes, now); break; case PacketKind.Contribution: ProcessContributionPacket(result.Sender, result.Bytes); break; case PacketKind.State: ProcessStatePacket(result.Sender, result.Bytes); break; } } } private static void ProcessHello(ulong sender, byte[] bytes, float now) { if (SNet.IsMaster && PacketCodec.TryDecodeHello(bytes, out var protocol)) { ClientPresenceById[sender] = new ClientPresence(protocol, now); } } private static void ProcessContributionPacket(ulong sender, byte[] bytes) { if (SNet.IsMaster && PacketCodec.TryDecodeContribution(bytes, out var data)) { ApplyContribution(sender, data); } } private static void ProcessStatePacket(ulong sender, byte[] bytes) { if (!SNet.IsMaster && SNet.HasMaster && sender == SNet.Master.Lookup && PacketCodec.TryDecodeState(bytes, out StateData state)) { ApplyState(state); } } private static void SendHello(float now) { ulong lookup = SNet.LocalPlayer.Lookup; if (SNet.IsMaster) { ClientPresenceById[lookup] = new ClientPresence(2, now); } else if (SNet.HasMaster) { NetworkAPI.InvokeFreeSizedEvent("qlsl.sharedstamina.hello.v2", PacketCodec.EncodeHello(), SNet.Master, (SNet_ChannelType)4); } } private static void SendContribution() { if (_epoch != 0L && SNet.HasLocalPlayer) { ContributionData contribution = new ContributionData(_epoch, _localCumulative); if (SNet.IsMaster) { ApplyContribution(SNet.LocalPlayer.Lookup, contribution); } else if (SNet.HasMaster) { NetworkAPI.InvokeFreeSizedEvent("qlsl.sharedstamina.contribution.v2", PacketCodec.EncodeContribution(contribution.Epoch, contribution.Cumulative), SNet.Master, (SNet_ChannelType)4); } } } private static void UpdateHostReadiness(float now) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown HashSet humanPlayerIds = GetHumanPlayerIds(); _humanPlayers = humanPlayerIds.Count; _moddedPlayers = 0; foreach (ulong item in humanPlayerIds) { if (ClientPresenceById.TryGetValue(item, out var value) && value.Protocol == 2 && now - value.LastSeen <= Plugin.ClientTimeout.Value) { _moddedPlayers++; } } bool flag = default(bool); if (_humanPlayers != _lastLoggedHumanPlayers || _moddedPlayers != _lastLoggedModdedPlayers) { ManualLogSource logger = Plugin.Logger; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(46, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Participant scan: humans="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_humanPlayers); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", modded="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_moddedPlayers); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", protocol="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logger.LogInfo(val); _lastLoggedHumanPlayers = _humanPlayers; _lastLoggedModdedPlayers = _moddedPlayers; } bool flag2 = _humanPlayers > 0 && _moddedPlayers == _humanPlayers; int num = Math.Clamp(_moddedPlayers, 1, 4); bool flag3 = Math.Abs(_capacity - (double)num) > 0.001; if (flag2 && (!_hostActive || flag3)) { StartHostEpoch(num); } else if (!flag2 && _hostActive) { _hostActive = false; _sharedActive = false; _lastAppliedRatio = float.NaN; ManualLogSource logger2 = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(76, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Shared stamina paused: every participating human needs protocol "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(2); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" (modded "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(_moddedPlayers); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(_humanPlayers); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(")."); } logger2.LogWarning(val2); } } private static void StartHostEpoch(int participantCount) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown double num = ((_sharedActive && _capacity > 0.0) ? Math.Clamp(_predictedPool / _capacity, 0.0, 1.0) : 1.0); _epoch = DateTime.UtcNow.Ticks ^ (long)SNet.LocalPlayer.Lookup; if (_epoch == 0L) { _epoch = 1L; } _capacity = Math.Clamp(participantCount, 1, 4); _serverPool = num * _capacity; _predictedPool = _serverPool; _localCumulative = 0.0; _localAcknowledged = 0.0; _lastAppliedRatio = float.NaN; _loggedStaminaHook = false; HostCumulativeById.Clear(); _hostActive = true; _sharedActive = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(59, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Shared stamina activated: participants="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(participantCount); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", capacity=x"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_capacity, "0"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", pool="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_serverPool, "0.000"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logger.LogInfo(val); } private static void ApplyContribution(ulong sender, ContributionData contribution) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (!_hostActive || contribution.Epoch != _epoch || !IsCurrentHuman(sender) || !ClientPresenceById.TryGetValue(sender, out var value) || value.Protocol != 2) { return; } HostCumulativeById.TryGetValue(sender, out var value2); double num = contribution.Cumulative - value2; if (Math.Abs(num) > 2.0) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Rejected suspicious stamina delta "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num, "0.000"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" from "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(sender); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logger.LogWarning(val); } else { HostCumulativeById[sender] = contribution.Cumulative; _serverPool = ClampPool(_serverPool + num); } } private static void BroadcastHostState() { StateData state = BuildHostState(); ApplyState(state); NetworkAPI.InvokeFreeSizedEvent("qlsl.sharedstamina.state.v2", PacketCodec.EncodeState(state), (SNet_ChannelType)4); } private static StateData BuildHostState() { StateData stateData = new StateData { Epoch = _epoch, Active = _hostActive, Pool = _serverPool, Capacity = _capacity, ParticipantCount = (int)_capacity, HumanPlayers = _humanPlayers, ModdedPlayers = _moddedPlayers }; foreach (KeyValuePair item in HostCumulativeById) { stateData.Acks.Add(new AckData(item.Key, item.Value)); } return stateData; } private static void ApplyState(StateData state) { _humanPlayers = state.HumanPlayers; _moddedPlayers = state.ModdedPlayers; if (!state.Active) { _sharedActive = false; _lastAppliedRatio = float.NaN; return; } if (state.Epoch != _epoch) { _epoch = state.Epoch; _localCumulative = 0.0; _localAcknowledged = 0.0; _lastAppliedRatio = float.NaN; } _capacity = Math.Max(1.0, state.Capacity); _serverPool = Math.Clamp(state.Pool, 0.0, _capacity); _sharedActive = true; ulong num = (SNet.HasLocalPlayer ? SNet.LocalPlayer.Lookup : 0); double localAcknowledged = 0.0; foreach (AckData ack in state.Acks) { if (ack.PlayerId == num) { localAcknowledged = ack.Cumulative; break; } } _localAcknowledged = localAcknowledged; _predictedPool = ClampPool(_serverPool + (_localCumulative - _localAcknowledged)); } private static HashSet GetHumanPlayerIds() { HashSet hashSet = new HashSet(); List playing = SNet_PlayerSendGroupManager.Playing; if (playing == null) { return hashSet; } for (int i = 0; i < playing.Count; i++) { SNet_Player val = playing[i]; if ((Object)(object)val != (Object)null && !val.IsBot && val.IsInGame) { hashSet.Add(val.Lookup); } } return hashSet; } private static bool IsCurrentHuman(ulong playerId) { return GetHumanPlayerIds().Contains(playerId); } private static bool HasPlayableLocalPlayer() { try { return SNet.HasLocalPlayer && PlayerManager.HasLocalPlayerAgent(); } catch { return false; } } private static double ClampPool(double value) { return Math.Clamp(value, 0.0, Math.Max(1.0, _capacity)); } private static void ResetForLevel() { _sessionPresent = true; _hostActive = false; _sharedActive = false; _epoch = 0L; _capacity = 1.0; _serverPool = 1.0; _predictedPool = 1.0; _localCumulative = 0.0; _localAcknowledged = 0.0; _lastAppliedRatio = float.NaN; _nextHelloTime = 0f; _nextNetworkTime = 0f; _humanPlayers = 0; _moddedPlayers = 0; _lastLoggedHumanPlayers = -1; _lastLoggedModdedPlayers = -1; _loggedStaminaHook = false; ClientPresenceById.Clear(); HostCumulativeById.Clear(); InboundPacket result; while (InboundPackets.TryDequeue(out result)) { } } private static void ResetAll() { ResetForLevel(); _sessionPresent = false; } } [HarmonyPatch(typeof(PlayerStamina), "LateUpdate")] internal static class StaminaPatch { [HarmonyPostfix] private static void Postfix(PlayerStamina __instance) { SharedStaminaController.AfterLocalStaminaUpdate(__instance); } } }