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 SurviveBase; internal static ConfigEntry DeathPenaltyBase; internal static ConfigEntry ScrapBonusPerItem; internal static ConfigEntry MaxPointsPerStat; internal static ConfigEntry RewardEasy; internal static ConfigEntry RewardMedium; internal static ConfigEntry RewardHard; internal static ConfigEntry RewardVeryHard; internal static ConfigEntry RewardExtreme; internal static ConfigEntry PenaltyEasy; internal static ConfigEntry PenaltyMedium; internal static ConfigEntry PenaltyHard; internal static ConfigEntry PenaltyVeryHard; internal static ConfigEntry PenaltyExtreme; internal static ConfigEntry HealthPercentPerPoint; internal static ConfigEntry StaminaPercentPerPoint; internal static ConfigEntry RegenPercentPerPoint; internal static ConfigEntry OxygenPercentPerPoint; internal static ConfigEntry WeightPercentPerPoint; internal static ConfigEntry JumpPercentPerPoint; internal static ConfigEntry RegenBasePerSecond; internal static ConfigEntry MoonRewardOverrides; internal static ConfigEntry MoonPenaltyOverrides; internal static ConfigEntry RiskMap; internal static ConfigEntry ExcludedMoonPatterns; internal static void Bind(ConfigFile config) { SurviveBase = config.Bind("Points", "SurviveBase", 3, "Base survive points (× moon reward mult). Scrap bonus is separate and not multiplied."); DeathPenaltyBase = config.Bind("Points", "DeathPenaltyBase", 3, "Points removed when dead at moon end = this × moon penalty mult. No scrap penalty. Floor 0."); ScrapBonusPerItem = config.Bind("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("Points", "MaxPointsPerStat", 100, "Maximum integer points you can put into a single stat."); RewardEasy = config.Bind("MoonMult.Reward", "Easy", 0.7f, "Survive multiplier for Easy moons."); RewardMedium = config.Bind("MoonMult.Reward", "Medium", 1f, "Survive multiplier for Medium moons."); RewardHard = config.Bind("MoonMult.Reward", "Hard", 1.2f, "Survive multiplier for Hard moons."); RewardVeryHard = config.Bind("MoonMult.Reward", "VeryHard", 1.35f, "Survive multiplier for VeryHard moons."); RewardExtreme = config.Bind("MoonMult.Reward", "Extreme", 1.5f, "Survive multiplier for Extreme moons."); PenaltyEasy = config.Bind("MoonMult.Penalty", "Easy", 1.4f, "Death penalty mult on Easy moons."); PenaltyMedium = config.Bind("MoonMult.Penalty", "Medium", 1f, "Death penalty mult on Medium moons."); PenaltyHard = config.Bind("MoonMult.Penalty", "Hard", 0.8f, "Death penalty mult on Hard moons."); PenaltyVeryHard = config.Bind("MoonMult.Penalty", "VeryHard", 0.6f, "Death penalty mult on VeryHard moons."); PenaltyExtreme = config.Bind("MoonMult.Penalty", "Extreme", 0.5f, "Death penalty mult on Extreme moons."); HealthPercentPerPoint = config.Bind("StatGrowth", "HealthPercentPerPoint", 0.01f, "health = base * (1 + points * this). 0.01 = +1% per point."); StaminaPercentPerPoint = config.Bind("StatGrowth", "StaminaPercentPerPoint", 0.01f, "Effective stamina factor per point."); RegenPercentPerPoint = config.Bind("StatGrowth", "RegenPercentPerPoint", 0.01f, "regen/sec = RegenBasePerSecond * (1 + points * this)."); OxygenPercentPerPoint = config.Bind("StatGrowth", "OxygenPercentPerPoint", 0.01f, "Underwater hold bonus per point."); WeightPercentPerPoint = config.Bind("StatGrowth", "WeightPercentPerPoint", 0.01f, "Carry weight penalty reduction per point."); JumpPercentPerPoint = config.Bind("StatGrowth", "JumpPercentPerPoint", 0.01f, "jumpForce = base * (1 + points * this) while jumping."); RegenBasePerSecond = config.Bind("StatGrowth", "RegenBasePerSecond", 0.01f, "Base HP/sec when Regen points > 0."); RiskMap = config.Bind("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("Moons", "RewardOverrides", "", "Per-moon survive mult overrides. Format: Experimentation:0.7,Rend:1.6"); MoonPenaltyOverrides = config.Bind("Moons", "PenaltyOverrides", "", "Per-moon death mult overrides. Format: Experimentation:1.5,Rend:0.4"); ExcludedMoonPatterns = config.Bind("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(); 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()); GUILayout.Label($"Max per stat: {ModConfig.MaxPointsPerStat.Value}", Array.Empty()); GUILayout.Space(8f); StatType[] order = Order; foreach (StatType statType in order) { GUILayout.BeginHorizontal(Array.Empty()); 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()); GUILayout.Label("Open with Escape. Survive moons to earn points.", Array.Empty()); GUILayout.Label("Dying at moon end costs points (floor 0).", Array.Empty()); 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 Seen = new HashSet(); 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 Allocated { get; } = new Dictionary { [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 item in Allocated) { num += (float)item.Value; } return num; } public int TotalAllocated() { int num = 0; foreach (KeyValuePair 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 dictionary = ParseKeyValues(ModConfig.RiskMap.Value); if (!string.IsNullOrEmpty(text) && dictionary.TryGetValue(text, out var value) && Enum.TryParse(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 map, string planetName, out float value) { value = 0f; if (string.IsNullOrWhiteSpace(planetName) || map.Count == 0) { return false; } foreach (KeyValuePair item in map) { if (planetName.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) >= 0) { value = item.Value; return true; } } return false; } private static Dictionary ParseKeyValues(string raw) { Dictionary dictionary = new Dictionary(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 ParseFloatMap(string raw) { Dictionary dictionary = new Dictionary(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) { } } }