Decompiled source of RunStats v1.0.1

BepInEx\plugins\RunStats\RunStats.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunStats.Data;
using RunStats.Patches;
using RunStats.Systems;
using RunStats.UI;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("flazaro")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Allocate run points into stamina, health, regen, oxygen, weight and jump. Escape menu + BepInEx config.")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+f671f3c798ca8ac3345359e23d47a79319d8a8ce")]
[assembly: AssemblyProduct("RunStats")]
[assembly: AssemblyTitle("RunStats")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.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 RunStats
{
	internal enum StatType
	{
		Health,
		Stamina,
		Regen,
		Oxygen,
		Weight,
		Jump
	}
	internal enum MoonDifficultyBucket
	{
		Easy,
		Medium,
		Hard,
		VeryHard,
		Extreme
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<int> SurviveBase;

		internal static ConfigEntry<int> DeathPenaltyBase;

		internal static ConfigEntry<float> ScrapBonusPerItem;

		internal static ConfigEntry<int> MaxPointsPerStat;

		internal static ConfigEntry<float> RewardEasy;

		internal static ConfigEntry<float> RewardMedium;

		internal static ConfigEntry<float> RewardHard;

		internal static ConfigEntry<float> RewardVeryHard;

		internal static ConfigEntry<float> RewardExtreme;

		internal static ConfigEntry<float> PenaltyEasy;

		internal static ConfigEntry<float> PenaltyMedium;

		internal static ConfigEntry<float> PenaltyHard;

		internal static ConfigEntry<float> PenaltyVeryHard;

		internal static ConfigEntry<float> PenaltyExtreme;

		internal static ConfigEntry<float> HealthPercentPerPoint;

		internal static ConfigEntry<float> StaminaPercentPerPoint;

		internal static ConfigEntry<float> RegenPercentPerPoint;

		internal static ConfigEntry<float> OxygenPercentPerPoint;

		internal static ConfigEntry<float> WeightPercentPerPoint;

		internal static ConfigEntry<float> JumpPercentPerPoint;

		internal static ConfigEntry<float> RegenBasePerSecond;

		internal static ConfigEntry<string> MoonRewardOverrides;

		internal static ConfigEntry<string> MoonPenaltyOverrides;

		internal static ConfigEntry<string> RiskMap;

		internal static ConfigEntry<string> ExcludedMoonPatterns;

		internal static void Bind(ConfigFile config)
		{
			SurviveBase = config.Bind<int>("Points", "SurviveBase", 3, "Base survive points (× moon reward mult). Scrap bonus is separate and not multiplied.");
			DeathPenaltyBase = config.Bind<int>("Points", "DeathPenaltyBase", 3, "Points removed when dead at moon end = this × moon penalty mult. No scrap penalty. Floor 0.");
			ScrapBonusPerItem = config.Bind<float>("Points", "ScrapBonusPerItem", 0.25f, "Extra points per scrap item brought to the ship this moon — only if you survive. 0.25 = quarter point per item.");
			MaxPointsPerStat = config.Bind<int>("Points", "MaxPointsPerStat", 100, "Maximum integer points you can put into a single stat.");
			RewardEasy = config.Bind<float>("MoonMult.Reward", "Easy", 0.7f, "Survive multiplier for Easy moons.");
			RewardMedium = config.Bind<float>("MoonMult.Reward", "Medium", 1f, "Survive multiplier for Medium moons.");
			RewardHard = config.Bind<float>("MoonMult.Reward", "Hard", 1.2f, "Survive multiplier for Hard moons.");
			RewardVeryHard = config.Bind<float>("MoonMult.Reward", "VeryHard", 1.35f, "Survive multiplier for VeryHard moons.");
			RewardExtreme = config.Bind<float>("MoonMult.Reward", "Extreme", 1.5f, "Survive multiplier for Extreme moons.");
			PenaltyEasy = config.Bind<float>("MoonMult.Penalty", "Easy", 1.4f, "Death penalty mult on Easy moons.");
			PenaltyMedium = config.Bind<float>("MoonMult.Penalty", "Medium", 1f, "Death penalty mult on Medium moons.");
			PenaltyHard = config.Bind<float>("MoonMult.Penalty", "Hard", 0.8f, "Death penalty mult on Hard moons.");
			PenaltyVeryHard = config.Bind<float>("MoonMult.Penalty", "VeryHard", 0.6f, "Death penalty mult on VeryHard moons.");
			PenaltyExtreme = config.Bind<float>("MoonMult.Penalty", "Extreme", 0.5f, "Death penalty mult on Extreme moons.");
			HealthPercentPerPoint = config.Bind<float>("StatGrowth", "HealthPercentPerPoint", 0.01f, "health = base * (1 + points * this). 0.01 = +1% per point.");
			StaminaPercentPerPoint = config.Bind<float>("StatGrowth", "StaminaPercentPerPoint", 0.01f, "Effective stamina factor per point.");
			RegenPercentPerPoint = config.Bind<float>("StatGrowth", "RegenPercentPerPoint", 0.01f, "regen/sec = RegenBasePerSecond * (1 + points * this).");
			OxygenPercentPerPoint = config.Bind<float>("StatGrowth", "OxygenPercentPerPoint", 0.01f, "Underwater hold bonus per point.");
			WeightPercentPerPoint = config.Bind<float>("StatGrowth", "WeightPercentPerPoint", 0.01f, "Carry weight penalty reduction per point.");
			JumpPercentPerPoint = config.Bind<float>("StatGrowth", "JumpPercentPerPoint", 0.01f, "jumpForce = base * (1 + points * this) while jumping.");
			RegenBasePerSecond = config.Bind<float>("StatGrowth", "RegenBasePerSecond", 0.01f, "Base HP/sec when Regen points > 0.");
			RiskMap = config.Bind<string>("Moons", "RiskMap", "D:Easy,C:Easy,B:Medium,A:Hard,S:VeryHard,S+:Extreme,Unknown:Medium", "Maps SelectableLevel.riskLevel to Easy/Medium/Hard/VeryHard/Extreme.");
			MoonRewardOverrides = config.Bind<string>("Moons", "RewardOverrides", "", "Per-moon survive mult overrides. Format: Experimentation:0.7,Rend:1.6");
			MoonPenaltyOverrides = config.Bind<string>("Moons", "PenaltyOverrides", "", "Per-moon death mult overrides. Format: Experimentation:1.5,Rend:0.4");
			ExcludedMoonPatterns = config.Bind<string>("Moons", "ExcludedMoonPatterns", "Company,CompanyBuilding,Gordion,71", "No points when leaving these moons (company / sell hub). Substring match on planet name, level name, levelID.");
		}

		internal static float PercentFor(StatType stat)
		{
			return stat switch
			{
				StatType.Health => HealthPercentPerPoint.Value, 
				StatType.Stamina => StaminaPercentPerPoint.Value, 
				StatType.Regen => RegenPercentPerPoint.Value, 
				StatType.Oxygen => OxygenPercentPerPoint.Value, 
				StatType.Weight => WeightPercentPerPoint.Value, 
				StatType.Jump => JumpPercentPerPoint.Value, 
				_ => 0f, 
			};
		}

		internal static float RewardMult(MoonDifficultyBucket bucket)
		{
			return bucket switch
			{
				MoonDifficultyBucket.Easy => RewardEasy.Value, 
				MoonDifficultyBucket.Medium => RewardMedium.Value, 
				MoonDifficultyBucket.Hard => RewardHard.Value, 
				MoonDifficultyBucket.VeryHard => RewardVeryHard.Value, 
				MoonDifficultyBucket.Extreme => RewardExtreme.Value, 
				_ => 1f, 
			};
		}

		internal static float PenaltyMult(MoonDifficultyBucket bucket)
		{
			return bucket switch
			{
				MoonDifficultyBucket.Easy => PenaltyEasy.Value, 
				MoonDifficultyBucket.Medium => PenaltyMedium.Value, 
				MoonDifficultyBucket.Hard => PenaltyHard.Value, 
				MoonDifficultyBucket.VeryHard => PenaltyVeryHard.Value, 
				MoonDifficultyBucket.Extreme => PenaltyExtreme.Value, 
				_ => 1f, 
			};
		}

		internal static float Scale(float baseValue, StatType stat, int points)
		{
			if (points <= 0)
			{
				return baseValue;
			}
			return baseValue * (1f + (float)points * PercentFor(stat));
		}
	}
	[BepInPlugin("flazaro.RunStats", "RunStats", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "flazaro.RunStats";

		public const string ModName = "RunStats";

		public const string ModVersion = "1.0.1";

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			new Harmony("flazaro.RunStats").PatchAll();
			GameObject val = new GameObject("RunStats.StatsMenu");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<StatsMenuBehaviour>();
			Log.LogInfo((object)"RunStats v1.0.1 loaded. Open Escape to allocate points.");
		}
	}
}
namespace RunStats.UI
{
	internal static class StatsMenu
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static WindowFunction <0>__DrawWindow;
		}

		private static readonly StatType[] Order = new StatType[6]
		{
			StatType.Health,
			StatType.Stamina,
			StatType.Regen,
			StatType.Oxygen,
			StatType.Weight,
			StatType.Jump
		};

		private static Rect _window = new Rect(40f, 80f, 420f, 420f);

		internal static bool Visible { get; set; }

		internal static void Draw()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			Rect window = _window;
			object obj = <>O.<0>__DrawWindow;
			if (obj == null)
			{
				WindowFunction val = DrawWindow;
				<>O.<0>__DrawWindow = val;
				obj = (object)val;
			}
			_window = GUI.Window(1381322323, window, (WindowFunction)obj, "RunStats — allocate points");
		}

		private static void DrawWindow(int id)
		{
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			PlayerRunState local = RunStateManager.GetLocal();
			GUILayout.Label("Unspent points: " + PointEconomy.FormatPoints(local.UnspentPoints), Array.Empty<GUILayoutOption>());
			GUILayout.Label($"Max per stat: {ModConfig.MaxPointsPerStat.Value}", Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			StatType[] order = Order;
			foreach (StatType statType in order)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				int num = local.Get(statType);
				float num2 = ModConfig.PercentFor(statType) * 100f;
				float num3 = (float)num * ModConfig.PercentFor(statType) * 100f;
				GUILayout.Label($"{statType}  [{num}]  (+{num3:0.#}% @ {num2:0.#}%/pt)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) });
				if (GUILayout.Button("−", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }))
				{
					local.TryRemove(statType);
				}
				if (GUILayout.Button("+", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }))
				{
					local.TryAdd(statType);
				}
				GUILayout.EndHorizontal();
			}
			GUILayout.Space(12f);
			GUILayout.Label("Formula: value = base × (1 + points × percent)", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Open with Escape. Survive moons to earn points.", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Dying at moon end costs points (floor 0).", Array.Empty<GUILayoutOption>());
			GUI.DragWindow(new Rect(0f, 0f, 10000f, 24f));
		}
	}
}
namespace RunStats.Systems
{
	internal static class PointEconomy
	{
		internal static void SettleMoonEnd(bool playerDeadAtEnd)
		{
			if (RunStateManager.SettledThisDeparture)
			{
				return;
			}
			RunStateManager.SettledThisDeparture = true;
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			string text = val?.PlanetName ?? ((val != null) ? ((Object)val).name : null) ?? "Unknown";
			if (!RunStateManager.IsScrapMoon(val))
			{
				Plugin.Log.LogInfo((object)("RunStats: no points — excluded moon (" + text + ")"));
				return;
			}
			MoonDifficultyBucket moonDifficultyBucket = RunStateManager.ResolveBucket(val);
			PlayerRunState local = RunStateManager.GetLocal();
			float num;
			string text2;
			if (playerDeadAtEnd)
			{
				float penaltyMultiplier = RunStateManager.GetPenaltyMultiplier(val);
				num = -Mathf.Max(0, Mathf.RoundToInt((float)ModConfig.DeathPenaltyBase.Value * penaltyMultiplier));
				text2 = $"death ×{penaltyMultiplier:0.##} ({moonDifficultyBucket} / {text})";
			}
			else
			{
				float rewardMultiplier = RunStateManager.GetRewardMultiplier(val);
				float num2 = Mathf.Max(0, Mathf.RoundToInt((float)ModConfig.SurviveBase.Value * rewardMultiplier));
				float num3 = 0f;
				if (ModConfig.ScrapBonusPerItem.Value > 0f && RunStateManager.ScrapCollectedThisMoon > 0)
				{
					num3 = (float)RunStateManager.ScrapCollectedThisMoon * ModConfig.ScrapBonusPerItem.Value;
				}
				num = num2 + num3;
				text2 = ((num3 > 0f) ? $"survive ×{rewardMultiplier:0.##} ({moonDifficultyBucket}) +{FormatPoints(num3)} scrap ({RunStateManager.ScrapCollectedThisMoon} items)" : $"survive ×{rewardMultiplier:0.##} ({moonDifficultyBucket} / {text})");
			}
			ApplyDelta(local, num);
			string text3 = ((num >= 0f) ? ("+" + FormatPoints(num)) : FormatPoints(num));
			string text4 = "RunStats: " + text3 + " pts (" + text2 + ") | unspent " + FormatPoints(local.UnspentPoints);
			Plugin.Log.LogInfo((object)text4);
			HUDManager instance = HUDManager.Instance;
			if (instance != null)
			{
				instance.DisplayTip("RunStats", text3 + " points — " + text2 + "\nUnspent: " + FormatPoints(local.UnspentPoints), false, false, "LC_Tip1");
			}
		}

		private static void ApplyDelta(PlayerRunState state, float delta)
		{
			if (delta > 0f)
			{
				state.AddUnspent(delta);
			}
			else
			{
				if (delta >= 0f)
				{
					return;
				}
				int num = Mathf.Max(0, Mathf.RoundToInt(0f - delta));
				int num2 = Mathf.Min(Mathf.FloorToInt(state.UnspentPoints), num);
				state.UnspentPoints -= num2;
				num -= num2;
				if (state.UnspentPoints < 0f)
				{
					state.UnspentPoints = 0f;
				}
				if (num <= 0)
				{
					return;
				}
				StatType[] array = new StatType[6]
				{
					StatType.Jump,
					StatType.Weight,
					StatType.Oxygen,
					StatType.Regen,
					StatType.Stamina,
					StatType.Health
				};
				foreach (StatType statType in array)
				{
					while (num > 0 && state.Get(statType) > 0)
					{
						state.Allocated[statType]--;
						num--;
					}
				}
				state.UnspentPoints = Math.Max(0f, state.UnspentPoints);
			}
		}

		internal static bool IsLocalDead(PlayerControllerB? player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return true;
			}
			if (!player.isPlayerDead)
			{
				return !player.isPlayerControlled;
			}
			return true;
		}

		internal static string FormatPoints(float value)
		{
			if (Mathf.Approximately(value % 1f, 0f))
			{
				return Mathf.RoundToInt(value).ToString(CultureInfo.InvariantCulture);
			}
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}
	}
}
namespace RunStats.Patches
{
	[HarmonyPatch]
	internal static class EscapeMenuPatches
	{
		[HarmonyPatch(typeof(QuickMenuManager), "Update")]
		[HarmonyPostfix]
		private static void QuickMenuUpdate(QuickMenuManager __instance)
		{
			try
			{
				StatsMenu.Visible = (Object)(object)__instance != (Object)null && __instance.isMenuOpen;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("EscapeMenuPatches: " + ex.Message));
			}
		}
	}
	internal sealed class StatsMenuBehaviour : MonoBehaviour
	{
		private void OnGUI()
		{
			if (StatsMenu.Visible)
			{
				StatsMenu.Draw();
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB))]
	internal static class PlayerStatPatches
	{
		private static float _sprintBefore;

		private static float _weightBefore;

		private static float _defaultJumpForce = -1f;

		private static float _jumpForceBeforeJump;

		private static bool IsLocalOwner(PlayerControllerB player)
		{
			if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).IsOwner && player.isPlayerControlled)
			{
				return !player.isPlayerDead;
			}
			return false;
		}

		[HarmonyPatch("ConnectClientToPlayerObject")]
		[HarmonyPostfix]
		private static void CaptureJumpForce(PlayerControllerB __instance)
		{
			if (IsLocalOwner(__instance) && _defaultJumpForce < 0f)
			{
				_defaultJumpForce = __instance.jumpForce;
			}
		}

		[HarmonyPatch("Update")]
		[HarmonyPrefix]
		private static void UpdatePrefix(PlayerControllerB __instance)
		{
			if (IsLocalOwner(__instance))
			{
				_sprintBefore = __instance.sprintMeter;
				_weightBefore = __instance.carryWeight;
				int points = RunStateManager.GetPoints(StatType.Weight);
				if (points > 0)
				{
					float num = Mathf.Max(0f, __instance.carryWeight - 1f) * (1f - Mathf.Clamp01((float)points * ModConfig.PercentFor(StatType.Weight)));
					__instance.carryWeight = 1f + Mathf.Max(0f, num);
				}
			}
		}

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void UpdatePostfix(PlayerControllerB __instance)
		{
			if (!IsLocalOwner(__instance))
			{
				return;
			}
			__instance.carryWeight = _weightBefore;
			int points = RunStateManager.GetPoints(StatType.Stamina);
			if (points > 0)
			{
				float num = __instance.sprintMeter - _sprintBefore;
				float num2 = ModConfig.PercentFor(StatType.Stamina);
				if (num < 0f)
				{
					float num3 = 1f / (1f + (float)points * num2);
					__instance.sprintMeter = Mathf.Clamp01(_sprintBefore + num * num3);
				}
				else if (num > 0f)
				{
					float num4 = 1f + (float)points * num2;
					__instance.sprintMeter = Mathf.Clamp01(_sprintBefore + num * num4);
				}
			}
			ApplyHealthCapAndRegen(__instance);
			ApplyOxygen(__instance);
		}

		[HarmonyPatch("LateUpdate")]
		[HarmonyPrefix]
		private static void LateUpdatePrefix(PlayerControllerB __instance)
		{
			if (IsLocalOwner(__instance))
			{
				_sprintBefore = __instance.sprintMeter;
				_weightBefore = __instance.carryWeight;
				int points = RunStateManager.GetPoints(StatType.Weight);
				if (points > 0)
				{
					float num = Mathf.Max(0f, __instance.carryWeight - 1f) * (1f - Mathf.Clamp01((float)points * ModConfig.PercentFor(StatType.Weight)));
					__instance.carryWeight = 1f + Mathf.Max(0f, num);
				}
			}
		}

		[HarmonyPatch("LateUpdate")]
		[HarmonyPostfix]
		private static void LateUpdatePostfix(PlayerControllerB __instance)
		{
			if (!IsLocalOwner(__instance))
			{
				return;
			}
			__instance.carryWeight = _weightBefore;
			int points = RunStateManager.GetPoints(StatType.Stamina);
			if (points > 0)
			{
				float num = __instance.sprintMeter - _sprintBefore;
				float num2 = ModConfig.PercentFor(StatType.Stamina);
				if (num < 0f)
				{
					float num3 = 1f / (1f + (float)points * num2);
					__instance.sprintMeter = Mathf.Clamp01(_sprintBefore + num * num3);
				}
				else if (num > 0f)
				{
					float num4 = 1f + (float)points * num2;
					__instance.sprintMeter = Mathf.Clamp01(_sprintBefore + num * num4);
				}
			}
		}

		[HarmonyPatch("Jump_performed")]
		[HarmonyPrefix]
		private static void JumpPrefix(PlayerControllerB __instance)
		{
			if (IsLocalOwner(__instance))
			{
				if (_defaultJumpForce < 0f)
				{
					_defaultJumpForce = __instance.jumpForce;
				}
				_jumpForceBeforeJump = __instance.jumpForce;
				int points = RunStateManager.GetPoints(StatType.Jump);
				__instance.jumpForce = ModConfig.Scale(_defaultJumpForce, StatType.Jump, points);
			}
		}

		[HarmonyPatch("Jump_performed")]
		[HarmonyPostfix]
		private static void JumpPostfix(PlayerControllerB __instance)
		{
			if (IsLocalOwner(__instance))
			{
				__instance.jumpForce = ((_jumpForceBeforeJump > 0f) ? _jumpForceBeforeJump : _defaultJumpForce);
			}
		}

		private static void ApplyHealthCapAndRegen(PlayerControllerB player)
		{
			int points = RunStateManager.GetPoints(StatType.Health);
			int points2 = RunStateManager.GetPoints(StatType.Regen);
			float num = ModConfig.Scale(100f, StatType.Health, points);
			if (player.health > Mathf.CeilToInt(num))
			{
				player.health = Mathf.CeilToInt(num);
			}
			if (points2 > 0 && player.health > 0 && !player.isPlayerDead && player.health < Mathf.FloorToInt(num))
			{
				RegenAccumulator.Add(ModConfig.Scale(ModConfig.RegenBasePerSecond.Value, StatType.Regen, points2) * Time.deltaTime, player, Mathf.FloorToInt(num));
			}
		}

		private static void ApplyOxygen(PlayerControllerB player)
		{
			int points = RunStateManager.GetPoints(StatType.Oxygen);
			if (points <= 0 || !player.isUnderwater)
			{
				return;
			}
			try
			{
				float num = (float)points * ModConfig.PercentFor(StatType.Oxygen);
				player.sprintMeter = Mathf.Clamp01(player.sprintMeter + num * 0.15f * Time.deltaTime);
			}
			catch
			{
			}
		}
	}
	internal static class RegenAccumulator
	{
		private static float _buffer;

		internal static void Add(float amount, PlayerControllerB player, int maxHp)
		{
			_buffer += amount;
			if (!(_buffer < 1f))
			{
				int num = Mathf.FloorToInt(_buffer);
				_buffer -= num;
				player.health = Mathf.Min(maxHp, player.health + num);
			}
		}
	}
	[HarmonyPatch]
	internal static class RoundEndPatches
	{
		[HarmonyPatch(typeof(StartOfRound), "ShipLeave")]
		[HarmonyPostfix]
		private static void ShipLeavePostfix()
		{
			try
			{
				PointEconomy.SettleMoonEnd(PointEconomy.IsLocalDead(RunStateManager.LocalPlayer()));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("RunStats ShipLeave: " + ex));
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "openingDoorsSequence")]
		[HarmonyPostfix]
		private static void OpeningDoorsPostfix()
		{
			RunStateManager.OnMoonStarted();
			ScrapTracker.Clear();
		}

		[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
		[HarmonyPostfix]
		private static void DisconnectPostfix()
		{
			RunStateManager.ResetRun();
		}

		[HarmonyPatch(typeof(StartOfRound), "FirePlayersAfterDeadlineClientRpc")]
		[HarmonyPostfix]
		private static void GameOverReset()
		{
			RunStateManager.ResetRun();
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SetItemInElevator")]
		[HarmonyPostfix]
		private static void SetItemInElevatorPostfix(PlayerControllerB __instance, bool droppedInShipRoom, bool droppedInElevator, GrabbableObject gObject)
		{
			if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).IsOwner && (droppedInShipRoom || droppedInElevator))
			{
				TryCountScrap(gObject);
			}
		}

		private static void TryCountScrap(GrabbableObject? obj)
		{
			if (!((Object)(object)obj == (Object)null) && !((Object)(object)obj.itemProperties == (Object)null) && obj.itemProperties.isScrap && !(ModConfig.ScrapBonusPerItem.Value <= 0f) && ScrapTracker.Mark(((Object)obj).GetInstanceID()))
			{
				RunStateManager.ScrapCollectedThisMoon++;
			}
		}
	}
	internal static class ScrapTracker
	{
		private static readonly HashSet<int> Seen = new HashSet<int>();

		internal static bool Mark(int id)
		{
			return Seen.Add(id);
		}

		internal static void Clear()
		{
			Seen.Clear();
		}
	}
}
namespace RunStats.Data
{
	internal sealed class PlayerRunState
	{
		public float UnspentPoints { get; set; }

		public Dictionary<StatType, int> Allocated { get; } = new Dictionary<StatType, int>
		{
			[StatType.Health] = 0,
			[StatType.Stamina] = 0,
			[StatType.Regen] = 0,
			[StatType.Oxygen] = 0,
			[StatType.Weight] = 0,
			[StatType.Jump] = 0
		};

		public int Get(StatType stat)
		{
			return Allocated[stat];
		}

		public float TotalPool()
		{
			float num = UnspentPoints;
			foreach (KeyValuePair<StatType, int> item in Allocated)
			{
				num += (float)item.Value;
			}
			return num;
		}

		public int TotalAllocated()
		{
			int num = 0;
			foreach (KeyValuePair<StatType, int> item in Allocated)
			{
				num += item.Value;
			}
			return num;
		}

		public bool TryAdd(StatType stat)
		{
			if (UnspentPoints < 1f)
			{
				return false;
			}
			if (Allocated[stat] >= ModConfig.MaxPointsPerStat.Value)
			{
				return false;
			}
			Allocated[stat]++;
			UnspentPoints -= 1f;
			if (UnspentPoints < 0f)
			{
				UnspentPoints = 0f;
			}
			return true;
		}

		public bool TryRemove(StatType stat)
		{
			if (Allocated[stat] <= 0)
			{
				return false;
			}
			Allocated[stat]--;
			UnspentPoints += 1f;
			return true;
		}

		public void AddUnspent(float amount)
		{
			if (!(amount <= 0f))
			{
				UnspentPoints += amount;
			}
		}

		public void ResetRun()
		{
			UnspentPoints = 0f;
			foreach (StatType value in Enum.GetValues(typeof(StatType)))
			{
				Allocated[value] = 0;
			}
		}
	}
	internal static class RunStateManager
	{
		private static readonly PlayerRunState Local = new PlayerRunState();

		internal static int ScrapCollectedThisMoon { get; set; }

		internal static bool SettledThisDeparture { get; set; }

		internal static PlayerRunState GetLocal()
		{
			return Local;
		}

		internal static int GetPoints(StatType stat)
		{
			return Local.Get(stat);
		}

		internal static void ResetRun()
		{
			Local.ResetRun();
			ScrapCollectedThisMoon = 0;
			SettledThisDeparture = false;
			Plugin.Log.LogInfo((object)"RunStats: run state reset.");
		}

		internal static void OnMoonStarted()
		{
			ScrapCollectedThisMoon = 0;
			SettledThisDeparture = false;
		}

		internal static PlayerControllerB? LocalPlayer()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return null;
			}
			return instance.localPlayerController;
		}

		internal static MoonDifficultyBucket ResolveBucket(SelectableLevel? level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return MoonDifficultyBucket.Medium;
			}
			string text = (level.riskLevel ?? string.Empty).Trim();
			Dictionary<string, string> dictionary = ParseKeyValues(ModConfig.RiskMap.Value);
			if (!string.IsNullOrEmpty(text) && dictionary.TryGetValue(text, out var value) && Enum.TryParse<MoonDifficultyBucket>(value, ignoreCase: true, out var result))
			{
				return result;
			}
			string text2 = text.ToUpperInvariant();
			if (text2.Contains("S+"))
			{
				return MoonDifficultyBucket.Extreme;
			}
			if (text2.StartsWith("S"))
			{
				return MoonDifficultyBucket.VeryHard;
			}
			if (text2.StartsWith("A"))
			{
				return MoonDifficultyBucket.Hard;
			}
			if (text2.StartsWith("B"))
			{
				return MoonDifficultyBucket.Medium;
			}
			if (text2.StartsWith("C") || text2.StartsWith("D"))
			{
				return MoonDifficultyBucket.Easy;
			}
			return MoonDifficultyBucket.Medium;
		}

		internal static float GetRewardMultiplier(SelectableLevel? level)
		{
			string planetName = level?.PlanetName ?? ((level != null) ? ((Object)level).name : null) ?? string.Empty;
			if (TryFindOverride(ParseFloatMap(ModConfig.MoonRewardOverrides.Value), planetName, out var value))
			{
				return value;
			}
			return ModConfig.RewardMult(ResolveBucket(level));
		}

		internal static float GetPenaltyMultiplier(SelectableLevel? level)
		{
			string planetName = level?.PlanetName ?? ((level != null) ? ((Object)level).name : null) ?? string.Empty;
			if (TryFindOverride(ParseFloatMap(ModConfig.MoonPenaltyOverrides.Value), planetName, out var value))
			{
				return value;
			}
			return ModConfig.PenaltyMult(ResolveBucket(level));
		}

		internal static bool IsScrapMoon(SelectableLevel? level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return false;
			}
			string text = level.PlanetName ?? string.Empty;
			string text2 = ((Object)level).name ?? string.Empty;
			string text3 = level.levelID.ToString(CultureInfo.InvariantCulture);
			string text4 = text + "|" + text2 + "|" + text3;
			if (string.IsNullOrWhiteSpace(ModConfig.ExcludedMoonPatterns.Value))
			{
				return true;
			}
			string[] array = ModConfig.ExcludedMoonPatterns.Value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string value = array[i].Trim();
				if (!string.IsNullOrEmpty(value) && text4.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryFindOverride(Dictionary<string, float> map, string planetName, out float value)
		{
			value = 0f;
			if (string.IsNullOrWhiteSpace(planetName) || map.Count == 0)
			{
				return false;
			}
			foreach (KeyValuePair<string, float> item in map)
			{
				if (planetName.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					value = item.Value;
					return true;
				}
			}
			return false;
		}

		private static Dictionary<string, string> ParseKeyValues(string raw)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrWhiteSpace(raw))
			{
				return dictionary;
			}
			string[] array = raw.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length == 2)
				{
					dictionary[array2[0].Trim()] = array2[1].Trim();
				}
			}
			return dictionary;
		}

		private static Dictionary<string, float> ParseFloatMap(string raw)
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrWhiteSpace(raw))
			{
				return dictionary;
			}
			string[] array = raw.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length == 2 && float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					dictionary[array2[0].Trim()] = result;
				}
			}
			return dictionary;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}