using System; 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 System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon.Movement; using Sparroh.UI; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Sparroh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LevelStatRewards")] [assembly: AssemblyTitle("LevelStatRewards")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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; } } } internal static class BonusStatPatches { [HarmonyPatch(typeof(Upgrade), "ApplyRandomStatUpgrade")] private static class ApplyRandomStatUpgrade_Patch { private static bool Prefix() { return false; } } [HarmonyPatch(typeof(Upgrade), "GetStatList")] private static class GetStatList_Patch { private static void Postfix(Upgrade __instance, ref string __result) { if (!((Object)(object)__instance == (Object)null) && __instance.ApplyStatUpgrades && !string.IsNullOrEmpty(__result)) { __result = StripBonusColorBlock(__result); } } } private static string StripBonusColorBlock(string properties) { int num = properties.IndexOf("", StringComparison.Ordinal); if (num < 0) { num = properties.IndexOf("", StringComparison.Ordinal); } if (num < 0) { return properties; } int num2 = properties.IndexOf("", num, StringComparison.Ordinal); if (num2 < 0) { return properties; } num2 += "".Length; StringBuilder stringBuilder = new StringBuilder(properties.Length); stringBuilder.Append(properties, 0, num); stringBuilder.Append(properties, num2, properties.Length - num2); string text = stringBuilder.ToString(); while (text.Contains("\n\n\n")) { text = text.Replace("\n\n\n", "\n\n"); } return text.Trim(); } } [HarmonyPatch(typeof(GearDetailsWindow))] internal static class GearDetailsWindowPatches { [HarmonyPostfix] [HarmonyPatch("Setup")] private static void Setup_Postfix(GearDetailsWindow __instance, IUpgradable upgradable) { try { if (upgradable != null) { GearStatButton.SetCurrentGear(upgradable); } } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogDebug((object)("GearDetails Setup: " + ex.Message)); } } } } public static class GearStatButton { public const string BarId = "levelstatrewards.stats"; public const int Order = 155; private static IUpgradable _currentGear; private static bool _registered; public static IUpgradable CurrentGear => _currentGear; public static void SetCurrentGear(IUpgradable gear) { _currentGear = gear; } public static void Tick() { if (!GearActionBar.IsGearMenuOpen()) { if (_registered) { GearActionBar.SetSlotVisible("levelstatrewards.stats", false); } return; } try { Menu instance = Menu.Instance; object obj; if (instance == null) { obj = null; } else { WindowSystem windowSystem = instance.WindowSystem; obj = ((windowSystem != null) ? windowSystem.GetTop() : null); } GearDetailsWindow val = (GearDetailsWindow)((obj is GearDetailsWindow) ? obj : null); if (val != null) { IUpgradable upgradablePrefab = val.UpgradablePrefab; if (upgradablePrefab != null) { _currentGear = upgradablePrefab; } } } catch { } if (_currentGear == null) { if (_registered) { GearActionBar.SetSlotVisible("levelstatrewards.stats", false); } } else { EnsureRegistered(); GearActionBar.SetSlotVisible("levelstatrewards.stats", true); GearActionBar.SetText("levelstatrewards.stats", BuildLabel(_currentGear)); GearActionBar.SetStyle("levelstatrewards.stats", (UIButtonStyle)(StatAllocationStore.GetUnspent(_currentGear) > 0)); } } public static void Unregister() { try { GearActionBar.Unregister("levelstatrewards.stats"); } catch { } _registered = false; _currentGear = null; } private static void EnsureRegistered() { GearActionBar.Register("levelstatrewards.stats", BuildLabel(_currentGear), 155, (Action)OnClicked, (UIButtonStyle)0); _registered = true; } private static string BuildLabel(IUpgradable gear) { if (gear == null) { return "Level Stats"; } int unspent = StatAllocationStore.GetUnspent(gear); if (unspent <= 0) { return "Level Stats"; } return $"Level Stats ({unspent})"; } private static void OnClicked() { try { if (_currentGear != null) { StatAllocationWindow.Open(_currentGear); } } catch (Exception arg) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogError((object)$"Open allocation UI failed: {arg}"); } } } } public static class ModBalance { public const int LevelsPerPoint = 3; public const int LevelCapForPoints = 30; public const int MaxPointsPerGear = 10; public const float PercentPerPoint = 10f; public const float PercentPerPointMinBetter = -10f; } [BepInPlugin("sparroh.levelstatrewards", "LevelStatRewards", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [MycoMod(/*Could not decode attribute arguments.*/)] public class LevelStatRewardsPlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.levelstatrewards"; public const string PluginName = "LevelStatRewards"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { StatAllocationStore.Load(); } catch (Exception arg) { Log.LogError((object)$"Init failed: {arg}"); return; } _harmony = new Harmony("sparroh.levelstatrewards"); try { _harmony.PatchAll(typeof(LevelStatRewardsPlugin).Assembly); Log.LogInfo((object)"LevelStatRewards v1.0.0 loaded."); } catch (Exception arg2) { Log.LogError((object)$"Harmony patch failed: {arg2}"); } } private void Update() { try { GearStatButton.Tick(); StatAllocationWindow.Tick(); } catch (Exception arg) { Log.LogError((object)$"Update error: {arg}"); } } private void OnDestroy() { try { StatAllocationWindow.DestroyUi(); GearStatButton.Unregister(); StatAllocationStore.Save(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogError((object)$"OnDestroy error: {arg}"); } } } } public static class StatAllocationStore { private sealed class EmptyMap : Dictionary { public static readonly EmptyMap Instance = new EmptyMap(); } private static readonly Dictionary> Allocations = new Dictionary>(StringComparer.Ordinal); private static string FilePath => Path.Combine(Paths.ConfigPath, "sparroh.levelstatrewards.allocations.json"); public static void Load() { Allocations.Clear(); try { if (File.Exists(FilePath)) { Parse(File.ReadAllText(FilePath)); ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogInfo((object)$"Loaded allocations for {Allocations.Count} gear entries."); } } } catch (Exception ex) { ManualLogSource log2 = LevelStatRewardsPlugin.Log; if (log2 != null) { log2.LogError((object)("Failed to load allocations: " + ex.Message)); } } } public static void Save() { try { string directoryName = Path.GetDirectoryName(FilePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(FilePath, Serialize()); } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogError((object)("Failed to save allocations: " + ex.Message)); } } } public static string GetGearKey(IUpgradable gear) { if ((Object)(object)((gear != null) ? gear.Info : null) == (Object)null) { return null; } return gear.Info.ID.ToString(); } public static int GetPoints(string gearKey, string statId) { if (string.IsNullOrEmpty(gearKey) || string.IsNullOrEmpty(statId)) { return 0; } if (!Allocations.TryGetValue(gearKey, out var value)) { return 0; } if (!value.TryGetValue(statId, out var value2)) { return 0; } return Mathf.Max(0, value2); } public static int GetSpent(string gearKey) { if (string.IsNullOrEmpty(gearKey) || !Allocations.TryGetValue(gearKey, out var value)) { return 0; } int num = 0; foreach (KeyValuePair item in value) { num += Mathf.Max(0, item.Value); } return num; } public static int GetGearLevel(IUpgradable gear) { try { GearData gearData = PlayerData.GetGearData(gear); return (gearData == null) ? 1 : gearData.Level; } catch { return 1; } } public static int GetTotalPoints(IUpgradable gear) { int num = Mathf.Min(GetGearLevel(gear), 30); return Mathf.Min(Mathf.Max(0, num / 3), 10); } public static int GetUnspent(IUpgradable gear) { string gearKey = GetGearKey(gear); return Mathf.Max(0, GetTotalPoints(gear) - GetSpent(gearKey)); } public static bool TryAddPoint(IUpgradable gear, string statId) { if (gear == null) { return false; } string gearKey = GetGearKey(gear); if (string.IsNullOrEmpty(gearKey) || StatDefinitions.Find(statId) == null) { return false; } if (GetUnspent(gear) <= 0) { return false; } if (!Allocations.TryGetValue(gearKey, out var value)) { value = new Dictionary(StringComparer.Ordinal); Allocations[gearKey] = value; } value.TryGetValue(statId, out var value2); value[statId] = value2 + 1; Save(); return true; } public static bool TryRemovePoint(IUpgradable gear, string statId) { if (gear == null) { return false; } string gearKey = GetGearKey(gear); if (string.IsNullOrEmpty(gearKey) || !Allocations.TryGetValue(gearKey, out var value)) { return false; } if (!value.TryGetValue(statId, out var value2) || value2 <= 0) { return false; } value2--; if (value2 <= 0) { value.Remove(statId); } else { value[statId] = value2; } if (value.Count == 0) { Allocations.Remove(gearKey); } Save(); return true; } public static void ResetGear(IUpgradable gear) { string gearKey = GetGearKey(gear); if (!string.IsNullOrEmpty(gearKey) && Allocations.Remove(gearKey)) { Save(); } } public static void ClampToBudget(IUpgradable gear) { string gearKey = GetGearKey(gear); if (string.IsNullOrEmpty(gearKey) || !Allocations.TryGetValue(gearKey, out var value)) { return; } int totalPoints = GetTotalPoints(gear); int num = 0; foreach (KeyValuePair item in value) { num += Mathf.Max(0, item.Value); } if (num <= totalPoints) { return; } List list = new List(value.Keys); list.Sort(StringComparer.Ordinal); int num2 = num - totalPoints; int num3 = list.Count - 1; while (num3 >= 0 && num2 > 0) { string key = list[num3]; int num4 = value[key]; int num5 = Mathf.Min(num4, num2); num4 -= num5; num2 -= num5; if (num4 <= 0) { value.Remove(key); } else { value[key] = num4; } num3--; } if (value.Count == 0) { Allocations.Remove(gearKey); } Save(); } public static IReadOnlyDictionary GetMap(string gearKey) { if (string.IsNullOrEmpty(gearKey) || !Allocations.TryGetValue(gearKey, out var value)) { return EmptyMap.Instance; } return value; } private static void Parse(string text) { text = text.Trim(); if (text.Length < 2 || text[0] != '{') { return; } int i = 1; while (i < text.Length) { SkipWs(text, ref i); if (i >= text.Length || text[i] == '}') { break; } if (text[i] == ',') { i++; continue; } if (!TryReadString(text, ref i, out var value)) { break; } SkipWs(text, ref i); if (i >= text.Length || text[i] != ':') { break; } i++; SkipWs(text, ref i); if (i >= text.Length || text[i] != '{') { break; } i++; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); while (i < text.Length) { SkipWs(text, ref i); if (i >= text.Length || text[i] == '}') { i++; break; } if (text[i] == ',') { i++; continue; } if (!TryReadString(text, ref i, out var value2)) { break; } SkipWs(text, ref i); if (i >= text.Length || text[i] != ':') { break; } i++; SkipWs(text, ref i); if (!TryReadInt(text, ref i, out var value3)) { break; } if (value3 > 0) { dictionary[value2] = value3; } } if (dictionary.Count > 0) { Allocations[value] = dictionary; } } } private static string Serialize() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n"); bool flag = true; foreach (KeyValuePair> allocation in Allocations) { if (allocation.Value == null || allocation.Value.Count == 0) { continue; } if (!flag) { stringBuilder.Append(",\n"); } flag = false; stringBuilder.Append(" \"").Append(Escape(allocation.Key)).Append("\": {\n"); bool flag2 = true; foreach (KeyValuePair item in allocation.Value) { if (item.Value > 0) { if (!flag2) { stringBuilder.Append(",\n"); } flag2 = false; stringBuilder.Append(" \"").Append(Escape(item.Key)).Append("\": ") .Append(item.Value); } } stringBuilder.Append("\n }"); } stringBuilder.Append("\n}\n"); return stringBuilder.ToString(); } private static string Escape(string s) { return s.Replace("\\", "\\\\").Replace("\"", "\\\""); } private static void SkipWs(string t, ref int i) { while (i < t.Length && char.IsWhiteSpace(t[i])) { i++; } } private static bool TryReadString(string t, ref int i, out string value) { value = null; SkipWs(t, ref i); if (i >= t.Length || t[i] != '"') { return false; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < t.Length) { char c = t[i++]; if (c == '\\' && i < t.Length) { stringBuilder.Append(t[i++]); continue; } if (c == '"') { value = stringBuilder.ToString(); return true; } stringBuilder.Append(c); } return false; } private static bool TryReadInt(string t, ref int i, out int value) { value = 0; SkipWs(t, ref i); int num = i; if (i < t.Length && t[i] == '-') { i++; } while (i < t.Length && char.IsDigit(t[i])) { i++; } if (i == num || (i == num + 1 && t[num] == '-')) { return false; } return int.TryParse(t.Substring(num, i - num), out value); } } public static class StatAllocationWindow { private sealed class Row { public StatDef Def; public RectTransform RowRt; public UIText Label; public UIText Value; public UIButton Minus; public UIButton Plus; } private static UIWindow _window; private static UIText _header; private static IUpgradable _gear; private static readonly List Rows = new List(); private static bool _pendingLayout; public static void Open(IUpgradable gear) { if (gear != null) { _gear = gear; StatAllocationStore.ClampToBudget(gear); EnsureWindow(); UIWindow window = _window; GearInfo info = gear.Info; window.WithTitle((((info != null) ? info.Name : null) ?? "Gear") + " — Level Stats"); _window.Show(); Rebuild(); ForceFullLayout(); _pendingLayout = true; } } public static void Tick() { if (_window == null || !_window.IsVisible) { return; } if (!GearActionBar.IsGearMenuOpen()) { _window.Hide(true); return; } if (_pendingLayout) { _pendingLayout = false; ForceFullLayout(); RefreshAllRows(); } RefreshHeader(); } public static void DestroyUi() { try { UIWindow window = _window; if (window != null) { window.Destroy(); } } catch { } _window = null; _header = null; Rows.Clear(); _gear = null; _pendingLayout = false; } private static void EnsureWindow() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (_window == null) { _window = UIWindow.Create("LevelStatRewards", (Vector2?)new Vector2(560f, 640f), "Level Stats", true, true, (int?)5200); _window.OnClose((Action)delegate { }); } } private static void Rebuild() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown Transform content = _window.Content; UIHelpers.DestroyChildren(content); Rows.Clear(); EnsureContentLayout(content); _header = UIText.Create(content, "Header", "", UITheme.ScaledFontBody, (Color?)UIColors.TextSecondary, (TextAlignmentOptions)514, true); GameObject gameObject = _header.GameObject; float? num = UITheme.S(72f); float? num2 = UITheme.S(56f); UIHelpers.EnsureLayoutElement(gameObject, (float?)null, num, num2); UIWindow.CreateSectionHeader(content, "Stats"); foreach (StatDef item in StatDefinitions.ForGear(_gear)) { Rows.Add(CreateRow(content, item)); } RectTransform obj = UIFactory.CreateRect("Footer", content); GameObject gameObject2 = ((Component)obj).gameObject; num2 = UITheme.S(44f); num = UITheme.S(40f); UIHelpers.EnsureLayoutElement(gameObject2, (float?)null, num2, num); UIFactory.AddHorizontalLayout(((Component)obj).gameObject, UITheme.S(8f), new RectOffset(0, 0, 4, 4), (TextAnchor)4, false, false, true, false); UIButton.Create((Transform)(object)obj, "Reset All", (Action)OnReset, (UIButtonStyle)2, (string)null, (float?)UITheme.S(32f)).SetWidth(UITheme.S(120f)); UIButton.Create((Transform)(object)obj, "Close", (Action)delegate { _window.Hide(true); }, (UIButtonStyle)0, (string)null, (float?)UITheme.S(32f)).SetWidth(UITheme.S(100f)); RefreshAllRows(); } private static void EnsureContentLayout(Transform content) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown if (!((Object)(object)content == (Object)null)) { VerticalLayoutGroup val = ((Component)content).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)content).gameObject.AddComponent(); ((HorizontalOrVerticalLayoutGroup)val).spacing = UITheme.S(6f); ((LayoutGroup)val).padding = new RectOffset(4, 4, 4, 4); } ((LayoutGroup)val).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)val).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)val).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)val).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)val).childForceExpandWidth = true; ContentSizeFitter val2 = ((Component)content).GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)content).gameObject.AddComponent(); } val2.horizontalFit = (FitMode)0; val2.verticalFit = (FitMode)2; } } private static Row CreateRow(Transform parent, StatDef def) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) RectTransform val = UIFactory.CreateRect("Row_" + def.Id, parent); GameObject gameObject = ((Component)val).gameObject; float? num = UITheme.S(40f); float? num2 = UITheme.S(36f); UIHelpers.EnsureLayoutElement(gameObject, (float?)null, num, num2); ((HorizontalOrVerticalLayoutGroup)UIFactory.AddHorizontalLayout(((Component)val).gameObject, UITheme.S(6f), new RectOffset(4, 4, 2, 2), (TextAnchor)3, true, false, true, false)).childForceExpandWidth = false; Image obj = ((Component)val).gameObject.AddComponent(); ((Graphic)obj).color = UIColors.WithAlpha(UIColors.PanelBg, 0.35f); UIFactory.ApplyWhiteSprite(obj); ((Graphic)obj).raycastTarget = false; UIText val2 = UIText.Create((Transform)(object)val, "Label", def.Label, UITheme.ScaledFontBody, (Color?)UIColors.TextPrimary, (TextAlignmentOptions)513, false); LayoutElement obj2 = UIHelpers.EnsureLayoutElement(val2.GameObject, (float?)UITheme.S(180f), (float?)null, (float?)null); obj2.flexibleWidth = 1f; obj2.minWidth = UITheme.S(100f); UIText val3 = UIText.Create((Transform)(object)val, "Value", "0", UITheme.ScaledFontBody, (Color?)UIColors.Sky, (TextAlignmentOptions)514, false); LayoutElement obj3 = UIHelpers.EnsureLayoutElement(val3.GameObject, (float?)UITheme.S(130f), (float?)null, (float?)null); obj3.minWidth = UITheme.S(100f); obj3.flexibleWidth = 0f; float num3 = UITheme.S(28f); float width = UITheme.S(40f); UIButton val4 = UIButton.Create((Transform)(object)val, "-", (Action)null, (UIButtonStyle)0, (string)null, (float?)num3); ConfigureFixedButton(val4, width, num3); UIButton val5 = UIButton.Create((Transform)(object)val, "+", (Action)null, (UIButtonStyle)1, (string)null, (float?)num3); ConfigureFixedButton(val5, width, num3); Row row = new Row { Def = def, RowRt = val, Label = val2, Value = val3, Minus = val4, Plus = val5 }; val4.OnClick((Action)delegate { OnMinus(row); }); val5.OnClick((Action)delegate { OnPlus(row); }); return row; } private static void ConfigureFixedButton(UIButton btn, float width, float height) { if (btn != null) { btn.SetWidth(width); LayoutElement obj = UIHelpers.EnsureLayoutElement(btn.GameObject, (float?)width, (float?)height, (float?)height); obj.minWidth = width; obj.flexibleWidth = 0f; obj.flexibleHeight = 0f; if ((Object)(object)btn.Rect != (Object)null) { btn.Rect.SetSizeWithCurrentAnchors((Axis)0, width); btn.Rect.SetSizeWithCurrentAnchors((Axis)1, height); } btn.SetInteractable(true); btn.ApplyVisualState(); btn.SetActive(true); } } private static void ForceFullLayout() { try { Canvas.ForceUpdateCanvases(); UIWindow window = _window; Transform obj = ((window != null) ? window.Content : null); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); if (val != null) { LayoutRebuilder.ForceRebuildLayoutImmediate(val); foreach (Row row in Rows) { if ((Object)(object)row?.RowRt != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(row.RowRt); } } LayoutRebuilder.ForceRebuildLayoutImmediate(val); } UIWindow window2 = _window; object obj2; if (window2 == null) { obj2 = null; } else { UIScrollView scrollView = window2.ScrollView; obj2 = ((scrollView != null) ? scrollView.Rect : null); } if ((Object)obj2 != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(_window.ScrollView.Rect); } Canvas.ForceUpdateCanvases(); } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogDebug((object)("ForceFullLayout: " + ex.Message)); } } } private static void OnPlus(Row row) { if (_gear != null && row?.Def != null && StatAllocationStore.TryAddPoint(_gear, row.Def.Id)) { AfterChange(); } } private static void OnMinus(Row row) { if (_gear != null && row?.Def != null && StatAllocationStore.TryRemovePoint(_gear, row.Def.Id)) { AfterChange(); } } private static void OnReset() { if (_gear != null) { StatAllocationStore.ResetGear(_gear); AfterChange(); } } private static void AfterChange() { RefreshAllRows(); try { StatApplier.RefreshAll(); } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogWarning((object)("Refresh after allocate: " + ex.Message)); } } } private static void RefreshHeader() { //IL_00a8: 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) if (_header != null && _gear != null) { int gearLevel = StatAllocationStore.GetGearLevel(_gear); int totalPoints = StatAllocationStore.GetTotalPoints(_gear); int spent = StatAllocationStore.GetSpent(StatAllocationStore.GetGearKey(_gear)); int num = Mathf.Max(0, totalPoints - spent); _header.Text = $"Level {gearLevel} · {totalPoints}/{10} points " + $"(1 per {3} lv, cap {30})\n" + $"Spent {spent} · Unspent {num}"; _header.Color = ((num > 0) ? UIColors.Shamrock : UIColors.TextSecondary); } } private static void RefreshAllRows() { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) RefreshHeader(); if (_gear == null) { return; } string gearKey = StatAllocationStore.GetGearKey(_gear); int unspent = StatAllocationStore.GetUnspent(_gear); foreach (Row row in Rows) { if (row?.Def != null) { int points = StatAllocationStore.GetPoints(gearKey, row.Def.Id); float num = row.Def.GetPercentPerPoint() * (float)points; string arg = ((num >= 0f) ? "+" : ""); row.Value.Text = ((points == 0) ? "0 pts" : $"{points} pts ({arg}{num:0.#}%)"); row.Value.Color = ((points > 0) ? UIColors.Sky : UIColors.TextMuted); UIButton minus = row.Minus; if (minus != null) { minus.SetActive(true); } UIButton plus = row.Plus; if (plus != null) { plus.SetActive(true); } UIButton minus2 = row.Minus; if (minus2 != null) { minus2.SetInteractable(points > 0); } UIButton plus2 = row.Plus; if (plus2 != null) { plus2.SetInteractable(unspent > 0); } UIButton minus3 = row.Minus; if (minus3 != null) { minus3.ApplyVisualState(); } UIButton plus3 = row.Plus; if (plus3 != null) { plus3.ApplyVisualState(); } } } } } public static class StatApplier { [HarmonyPatch(typeof(Player), "ApplyUpgrades")] private static class PlayerApplyUpgrades_Patch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && ((NetworkBehaviour)__instance).IsOwner) { ApplyToPlayer(__instance); } } } [HarmonyPatch(typeof(Gun), "OnUpgradesEnabled")] private static class GunOnUpgradesEnabled_Patch { private static void Postfix(Gun __instance) { ApplyToGear((IGear)(object)__instance); } } [HarmonyPatch(typeof(Throwable), "OnUpgradesEnabled")] private static class ThrowableOnUpgradesEnabled_Patch { private static void Postfix(Throwable __instance) { ApplyToGear((IGear)(object)__instance); } } private static FieldInfo _playerMoveSpeed; private static FieldInfo _playerSlideSpeed; private static FieldInfo _playerFullSlideDuration; private static FieldInfo _playerMaxHealth; private static FieldInfo _throwableCooldownData; private static bool _fieldsResolved; public static void RefreshAll() { try { if ((Object)(object)Player.LocalPlayer != (Object)null && ((NetworkBehaviour)Player.LocalPlayer).IsOwner) { Player.LocalPlayer.ApplyUpgrades(true); } if (!((Object)(object)Player.LocalPlayer == (Object)null)) { TryRefreshPlayerGear(Player.LocalPlayer); } } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogWarning((object)("RefreshAll failed: " + ex.Message)); } } } private static void TryRefreshPlayerGear(Player player) { try { PropertyInfo propertyInfo = AccessTools.Property(typeof(Player), "Gear"); object obj = null; if (propertyInfo != null) { obj = propertyInfo.GetValue(player); } else { FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "Gear"); if (fieldInfo != null) { obj = fieldInfo.GetValue(player); } } if (obj is IGear[] array) { IGear[] array2 = array; for (int i = 0; i < array2.Length; i++) { SafeApplyGear(array2[i]); } } else { if (!(obj is IList list)) { return; } { foreach (IGear item in list) { SafeApplyGear(item); } return; } } } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogDebug((object)("TryRefreshPlayerGear: " + ex.Message)); } } } private static void SafeApplyGear(IGear gear) { if (gear == null) { return; } try { gear.ApplyUpgrades(); } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogDebug((object)("ApplyUpgrades gear failed: " + ex.Message)); } } } public static void ApplyToPlayer(Player player) { //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !((NetworkBehaviour)player).IsOwner) { return; } Character character = player.Character; if ((Object)(object)character == (Object)null) { return; } StatAllocationStore.ClampToBudget((IUpgradable)(object)character); string gearKey = StatAllocationStore.GetGearKey((IUpgradable)(object)character); if (string.IsNullOrEmpty(gearKey)) { return; } EnsurePlayerFields(); int points = StatAllocationStore.GetPoints(gearKey, "run_speed"); int points2 = StatAllocationStore.GetPoints(gearKey, "slide_speed"); int points3 = StatAllocationStore.GetPoints(gearKey, "slide_duration"); int points4 = StatAllocationStore.GetPoints(gearKey, "max_health"); int points5 = StatAllocationStore.GetPoints(gearKey, "regen_delay"); int points6 = StatAllocationStore.GetPoints(gearKey, "healing"); if (points + points2 + points3 + points4 + points5 + points6 <= 0) { return; } try { if (points > 0 && _playerMoveSpeed != null) { float num = (float)_playerMoveSpeed.GetValue(player); _playerMoveSpeed.SetValue(player, num * StatDefinitions.Find("run_speed").MultiplierForPoints(points)); } if (points2 > 0 && _playerSlideSpeed != null) { float num2 = (float)_playerSlideSpeed.GetValue(player); _playerSlideSpeed.SetValue(player, num2 * StatDefinitions.Find("slide_speed").MultiplierForPoints(points2)); } if (points3 > 0 && _playerFullSlideDuration != null) { float num3 = (float)_playerFullSlideDuration.GetValue(player); _playerFullSlideDuration.SetValue(player, num3 * StatDefinitions.Find("slide_duration").MultiplierForPoints(points3)); } if (points4 > 0) { float num4 = StatDefinitions.Find("max_health").MultiplierForPoints(points4); if (_playerMaxHealth != null) { float num5 = (float)_playerMaxHealth.GetValue(player) * num4; _playerMaxHealth.SetValue(player, num5); try { player.SetMaxHealth(num5); } catch { } } } FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "upgradeVariables"); if (fieldInfo != null) { UpgradeVariableData val = (UpgradeVariableData)fieldInfo.GetValue(player); bool flag = false; if (points5 > 0) { val.noDamageTimeBeforeHealthRegen *= StatDefinitions.Find("regen_delay").MultiplierForPoints(points5); flag = true; } if (points6 > 0) { val.healingMultiplier *= StatDefinitions.Find("healing").MultiplierForPoints(points6); flag = true; } if (flag) { fieldInfo.SetValue(player, val); } } } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogWarning((object)("ApplyToPlayer failed: " + ex.Message)); } } } public static void ApplyToGear(IGear gear) { if (gear == null) { return; } object gear2 = ((object)((IUpgradable)gear).Prefab) ?? ((object)gear); StatAllocationStore.ClampToBudget((IUpgradable)gear2); string gearKey = StatAllocationStore.GetGearKey((IUpgradable)gear2); if (string.IsNullOrEmpty(gearKey)) { return; } try { Throwable val = (Throwable)(object)((gear is Throwable) ? gear : null); if (val != null) { ApplyThrowable(val, gearKey); return; } IWeapon val2 = (IWeapon)(object)((gear is IWeapon) ? gear : null); if (val2 != null) { ApplyWeapon(val2, gearKey); } } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogWarning((object)("ApplyToGear failed: " + ex.Message)); } } } private static void ApplyWeapon(IWeapon weapon, string key) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) int points = StatAllocationStore.GetPoints(key, "reload"); int points2 = StatAllocationStore.GetPoints(key, "magazine"); int points3 = StatAllocationStore.GetPoints(key, "ammo_capacity"); int points4 = StatAllocationStore.GetPoints(key, "recoil"); int points5 = StatAllocationStore.GetPoints(key, "spread"); if (points + points2 + points3 + points4 + points5 > 0) { ref GunData gunData = ref weapon.GunData; if (points > 0) { gunData.reloadDuration *= StatDefinitions.Find("reload").MultiplierForPoints(points); } if (points2 > 0) { float num = StatDefinitions.Find("magazine").MultiplierForPoints(points2); gunData.magazineSize = Mathf.Max(1, Mathf.RoundToInt((float)gunData.magazineSize * num)); } if (points3 > 0 && gunData.hasLimitedAmmo) { float num2 = StatDefinitions.Find("ammo_capacity").MultiplierForPoints(points3); gunData.ammoCapacity = Mathf.Max(0, Mathf.RoundToInt((float)gunData.ammoCapacity * num2)); } if (points4 > 0) { float num3 = StatDefinitions.Find("recoil").MultiplierForPoints(points4); ref Vector2 recoilX = ref gunData.recoilData.recoilX; recoilX *= num3; ref Vector2 recoilY = ref gunData.recoilData.recoilY; recoilY *= num3; ref Vector2 recoilZ = ref gunData.recoilData.recoilZ; recoilZ *= num3; ref Vector2 translateZ = ref gunData.recoilData.translateZ; translateZ *= num3; gunData.recoilData.maxRecoilZ *= num3; gunData.recoilData.maxTranslateZ *= num3; } if (points5 > 0) { float num4 = StatDefinitions.Find("spread").MultiplierForPoints(points5); ref Vector2 spreadSize = ref gunData.spreadData.spreadSize; spreadSize *= num4; gunData.firstShotSpreadMultiplier *= num4; } } } private static void ApplyThrowable(Throwable throwable, string key) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) int points = StatAllocationStore.GetPoints(key, "grenade_damage"); int points2 = StatAllocationStore.GetPoints(key, "grenade_cooldown"); int points3 = StatAllocationStore.GetPoints(key, "explosion_radius"); if (points + points2 + points3 <= 0) { return; } if (throwable != null) { ref GunData gunData = ref ((IWeapon)throwable).GunData; if (points > 0) { gunData.damage *= StatDefinitions.Find("grenade_damage").MultiplierForPoints(points); } if (points3 > 0) { gunData.hitForce *= StatDefinitions.Find("explosion_radius").MultiplierForPoints(points3); } } if (points2 <= 0) { return; } EnsureCooldownField(); if (!(_throwableCooldownData != null)) { return; } try { CooldownData val = (CooldownData)_throwableCooldownData.GetValue(throwable); float num = StatDefinitions.Find("grenade_cooldown").MultiplierForPoints(points2); val.rechargeDuration *= num; _throwableCooldownData.SetValue(throwable, val); } catch (Exception ex) { ManualLogSource log = LevelStatRewardsPlugin.Log; if (log != null) { log.LogDebug((object)("Cooldown apply: " + ex.Message)); } } } private static void EnsurePlayerFields() { if (!_fieldsResolved) { _fieldsResolved = true; _playerMoveSpeed = AccessTools.Field(typeof(Player), "moveSpeed"); _playerSlideSpeed = AccessTools.Field(typeof(Player), "slideSpeed"); _playerFullSlideDuration = AccessTools.Field(typeof(Player), "fullSlideDuration"); _playerMaxHealth = AccessTools.Field(typeof(Player), "maxHealth"); } } private static void EnsureCooldownField() { if (!(_throwableCooldownData != null)) { _throwableCooldownData = AccessTools.Field(typeof(Throwable), "cooldownData"); } } } public enum StatCategory { Employee, Weapon, Grenade } public sealed class StatDef { public string Id { get; } public string Label { get; } public StatCategory Category { get; } public float PercentPerPoint { get; } public bool InvertDisplay { get; } public StatDef(string id, string label, StatCategory category, float percentPerPoint, bool invertDisplay = false) { Id = id; Label = label; Category = category; PercentPerPoint = percentPerPoint; InvertDisplay = invertDisplay; } public float GetPercentPerPoint() { return PercentPerPoint; } public float MultiplierForPoints(int points) { if (points <= 0) { return 1f; } return 1f + PercentPerPoint / 100f * (float)points; } } public static class StatDefinitions { public const string RunSpeed = "run_speed"; public const string SlideSpeed = "slide_speed"; public const string SlideDuration = "slide_duration"; public const string MaxHealth = "max_health"; public const string RegenDelay = "regen_delay"; public const string Healing = "healing"; public const string Reload = "reload"; public const string Magazine = "magazine"; public const string AmmoCapacity = "ammo_capacity"; public const string Recoil = "recoil"; public const string Spread = "spread"; public const string GrenadeDamage = "grenade_damage"; public const string GrenadeCooldown = "grenade_cooldown"; public const string ExplosionRadius = "explosion_radius"; private const float P = 10f; private const float N = -10f; public static readonly StatDef[] Employee = new StatDef[6] { new StatDef("run_speed", "Run Speed", StatCategory.Employee, 10f), new StatDef("slide_speed", "Slide Speed", StatCategory.Employee, 10f), new StatDef("slide_duration", "Slide Duration", StatCategory.Employee, 10f), new StatDef("max_health", "Max Health", StatCategory.Employee, 10f), new StatDef("regen_delay", "Health Regen Delay", StatCategory.Employee, -10f, invertDisplay: true), new StatDef("healing", "Healing Received", StatCategory.Employee, 10f) }; public static readonly StatDef[] Weapon = new StatDef[5] { new StatDef("reload", "Reload Speed", StatCategory.Weapon, -10f, invertDisplay: true), new StatDef("magazine", "Magazine Size", StatCategory.Weapon, 10f), new StatDef("ammo_capacity", "Ammo Capacity", StatCategory.Weapon, 10f), new StatDef("recoil", "Recoil", StatCategory.Weapon, -10f, invertDisplay: true), new StatDef("spread", "Spread", StatCategory.Weapon, -10f, invertDisplay: true) }; public static readonly StatDef[] Grenade = new StatDef[3] { new StatDef("grenade_damage", "Damage", StatCategory.Grenade, 10f), new StatDef("grenade_cooldown", "Cooldown", StatCategory.Grenade, -10f, invertDisplay: true), new StatDef("explosion_radius", "Explosion Radius", StatCategory.Grenade, 10f) }; public static IReadOnlyList ForGear(IUpgradable gear) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 if (gear == null) { return Array.Empty(); } if (gear is Character) { return Employee; } if (gear is Throwable || gear is GrenadeGear) { return Grenade; } if (gear is IWeapon || gear is Gun) { return Weapon; } try { GearType gearType = gear.GearType; if ((int)gearType != 3) { if ((int)gearType == 5) { return Employee; } return Weapon; } return Grenade; } catch { return Weapon; } } public static StatDef Find(string id) { StatDef[] employee = Employee; foreach (StatDef statDef in employee) { if (statDef.Id == id) { return statDef; } } employee = Weapon; foreach (StatDef statDef2 in employee) { if (statDef2.Id == id) { return statDef2; } } employee = Grenade; foreach (StatDef statDef3 in employee) { if (statDef3.Id == id) { return statDef3; } } return null; } } namespace LevelStatRewards { public static class MyPluginInfo { public const string PLUGIN_GUID = "LevelStatRewards"; public const string PLUGIN_NAME = "LevelStatRewards"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }