Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RunStats v1.0.1
BepInEx\plugins\RunStats\RunStats.dll
Decompiled 4 hours agousing 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) { } } }