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 ProgressivePowers v0.3.7
plugins/ProgressivePowers.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using ProgressivePowers.Common; using ProgressivePowers.Modules; using SimpleJson; using TMPro; using UnityEngine; using UnityEngine.UI; using YamlDotNet.Serialization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ProgressivePowers")] [assembly: AssemblyDescription("Turns the Forsaken powers into passives that deepen as you kill bosses.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ProgressivePowers")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("6b1f0c74-9d2e-4a55-8f31-2c7a4e5b9d10")] [assembly: AssemblyFileVersion("0.3.7")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.7.0")] namespace ProgressivePowers { internal class Logger { public static LogLevel Level = (LogLevel)16; public static void EnableDebugLogging(object sender, EventArgs e) { CheckEnableDebugLogging(); } public static void CheckEnableDebugLogging() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ValConfig.EnableDebugMode.Value) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void SetDebugLogging(bool state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (state) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void LogDebug(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 32) { ProgressivePowers.Log.LogInfo((object)("[DEBUG]" + message)); } } public static void LogInfo(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 16) { ProgressivePowers.Log.LogInfo((object)message); } } public static void LogWarning(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 4) { ProgressivePowers.Log.LogWarning((object)message); } } public static void LogError(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 2) { ProgressivePowers.Log.LogError((object)message); } } } internal class ValConfig { public static ConfigFile cfg; public static ConfigEntry<bool> EnableDebugMode; public static ConfigEntry<int> DebugLevelOverride; public static ConfigEntry<PowerHud.DisplayMode> PowerDisplayMode; public static ConfigEntry<int> FullPowerDisplays; public static ConfigEntry<float> PowerDisplaySpacing; public static ConfigEntry<float> ConfigApplyDelay; public static ConfigEntry<float> KillCreditRange; public static ConfigEntry<bool> ShowLockedFeatures; public static ConfigEntry<bool> ShowPassiveInStatusBar; public static ConfigEntry<bool> BlockPowerActivation; public static ConfigEntry<int> MaxAttunedPowers; public static ConfigEntry<string> LevelRequirementsYaml; public static ConfigEntry<string> AdvancedPowersYaml; public static ConfigEntry<bool> EnableAdvancedPowers; public const string cfgFolder = "ProgressivePowers"; private static ConfigFileWatcher fileWatcher; public static int Generation { get; private set; } public static void MarkChanged() { Generation++; } public ValConfig(ConfigFile cf) { cfg = cf; cfg.SaveOnConfigSet = false; CreateConfigValues(cf); Logger.SetDebugLogging(EnableDebugMode.Value); } public static void SaveOnSet(bool enabled) { cfg.SaveOnConfigSet = enabled; cfg.Save(); } public static void WatchConfigFile() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (fileWatcher == null) { fileWatcher = new ConfigFileWatcher(cfg, 1000L); fileWatcher.OnConfigFileReloaded += delegate { Logger.LogDebug("Reloaded the config file from disk."); }; } } public static void PushSyncedValues() { if (cfg == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } bool saveOnConfigSet = cfg.SaveOnConfigSet; try { cfg.SaveOnConfigSet = false; cfg.Save(); cfg.Reload(); } catch (Exception ex) { Logger.LogError("Could not push the changed configuration to clients: " + ex.Message); } finally { cfg.SaveOnConfigSet = saveOnConfigSet; } } private void CreateConfigValues(ConfigFile Config) { //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_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Expected O, but got Unknown //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Expected O, but got Unknown //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Expected O, but got Unknown //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Expected O, but got Unknown EnableDebugMode = Config.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdvanced = true } })); EnableDebugMode.SettingChanged += Logger.EnableDebugLogging; Logger.CheckEnableDebugLogging(); DebugLevelOverride = Config.Bind<int>("Client config", "Debug Level Override", -1, new ConfigDescription("Forces every power to this mastery level regardless of kills, for testing, or to its highest level if it has fewer. -1 disables the override. Treated as a cheat: only honored after 'devcommands' is enabled in the console, on a world you host.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 100), new object[1] { (object)new ConfigurationManagerAttributes { IsAdvanced = true } })); PowerDisplayMode = Config.Bind<PowerHud.DisplayMode>("Client config", "Power Display Mode", PowerHud.DisplayMode.Compact, new ConfigDescription("How the HUD shows your attuned powers. Stacked: every one with the full display - icon, name and mastery level. Mixed: the first 'Full Power Displays' in full, the rest as one line each with a small icon, the name and the mastery level. Compact: the newest in full, the rest as one line each with just a small icon and the mastery level. Icons: all of them as small icons with their mastery level, in place of the usual display. NewestOnly: only the newest.", (AcceptableValueBase)null, Array.Empty<object>())); FullPowerDisplays = Config.Bind<int>("Client config", "Full Power Displays", 2, new ConfigDescription("With 'Power Display Mode' set to Mixed, how many attuned powers the HUD shows with the full display before listing the rest as one line each.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); PowerDisplaySpacing = Config.Bind<float>("Client config", "Power Display Spacing", 4f, new ConfigDescription("Space, in pixels, between full power displays stacked on the HUD, and before the first one-line entry.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); ConfigApplyDelay = BindServerConfig("General", "Config Apply Delay", 1f, "Delay in seconds before a changed config entry is applied in-game. Coalesces a burst of rapid edits (typing, file reloads, server sync) into a single apply. Set to 0 to apply instantly.", advanced: true, 0f, 10f); KillCreditRange = BindServerConfig("General", "Kill Credit Range", 200f, "How close you must be when a boss dies to be credited with the kill, in meters. Set to 0 for unlimited range.", advanced: false, 0f, 10000f); ShowLockedFeatures = BindServerConfig("General", "Show Locked Features", value: true, "Show not-yet-unlocked features greyed out, along with what you still need to kill."); ShowPassiveInStatusBar = BindServerConfig("General", "Show Passive In Status Bar", value: true, "Show each attuned power's passive as a permanent icon in the status effect bar."); BlockPowerActivation = BindServerConfig("General", "Block Power Activation", value: true, "Prevent Forsaken powers from being activated. Turning this off restores vanilla activation while keeping the passives, and activating then fires every attuned power at once."); MaxAttunedPowers = BindServerConfig("General", "Max Attuned Powers", 1, "How many Forsaken powers a character can be attuned to at once. Above 1, using the boss stone of a power you already hold releases it, and attuning to another while every slot is taken replaces the one attuned longest ago. Lowering this only sets the oldest attunements aside; they are dropped once the player next attunes or releases a power.", advanced: false, 1, 8); EnableAdvancedPowers = BindServerConfig("General", "Enable Advanced Power Config", value: false, "Define every power's levels in Powers.yaml instead of the Feature entries below. That file sets how many levels each power has, can put any effect this mod supports on any power at any level, several per level, and saving it applies live. While this is on, the 'Power N' sections of this file are ignored for every power that file lists."); LevelRequirementsYaml = cfg.Bind<string>("General", "Level Requirements", "", new ConfigDescription("Serialized contents of Levels.yaml. Edit the file, not this entry - it exists so the server can push requirements to clients.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, Browsable = false } })); AdvancedPowersYaml = cfg.Bind<string>("General", "Advanced Powers", "", new ConfigDescription("Serialized contents of Powers.yaml. Edit the file, not this entry - it exists so the server can push power definitions to clients.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, Browsable = false } })); PowerDefinitions.BindConfigs(); WireChangeNotifications(); } private static void WireChangeNotifications() { ShowLockedFeatures.SettingChanged += OnChanged; ShowPassiveInStatusBar.SettingChanged += OnChanged; BlockPowerActivation.SettingChanged += OnChanged; MaxAttunedPowers.SettingChanged += OnChanged; DebugLevelOverride.SettingChanged += OnChanged; EnableAdvancedPowers.SettingChanged += OnChanged; BossPower[] all = PowerDefinitions.All; for (int i = 0; i < all.Length; i++) { PowerFeature[] features = all[i].Features; foreach (PowerFeature powerFeature in features) { if (!(powerFeature is FloatFeature floatFeature)) { if (!(powerFeature is DamageModFeature damageModFeature)) { if (powerFeature is AttributeFeature attributeFeature) { attributeFeature.Cfg.SettingChanged += OnChanged; } } else { damageModFeature.Cfg.SettingChanged += OnChanged; } } else { floatFeature.Cfg.SettingChanged += OnChanged; } } } static void OnChanged(object sender, EventArgs e) { MarkChanged(); ConfigChangeDebouncer.Schedule("ValConfig", delegate { PassivePowerManager.Refresh(force: true); }); } } public static ConfigEntry<float[]> BindServerConfig(string category, string key, float[] value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f) { //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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float[]>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<bool> BindServerConfig(string category, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<bool>(category, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<int> BindServerConfig(string category, string key, int value, string description, bool advanced = false, int valMin = 0, int valMax = 150) { //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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<int>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<float> BindServerConfig(string category, string key, float value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f) { //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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<string> BindServerConfig(string category, string key, string value, string description, AcceptableValueList<string> acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<string>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } } internal static class AdvancedPowers { private const string FileName = "Powers.yaml"; internal const string ToggleName = "Enable Advanced Power Config"; private static Dictionary<string, Dictionary<int, List<PassiveEffect>>> table = new Dictionary<string, Dictionary<int, List<PassiveEffect>>>(); private static SyncedConfigFile file; internal static bool Enabled { get { if (ValConfig.EnableAdvancedPowers != null) { return ValConfig.EnableAdvancedPowers.Value; } return false; } } internal static void Initialize() { file = new SyncedConfigFile("Powers.yaml", ValConfig.AdvancedPowersYaml, BuildDefaultYaml); try { file.Initialize(); ReloadFromConfig(); } catch (Exception ex) { Logger.LogError("Could not initialize Powers.yaml; the built-in features stay in use: " + ex.Message); } ValConfig.AdvancedPowersYaml.SettingChanged += delegate { ConfigChangeDebouncer.Schedule("AdvancedPowers", delegate { ReloadFromConfig(); LevelRequirements.QueueCoverageReport(); ValConfig.MarkChanged(); PassivePowerManager.Refresh(force: true); }); }; } internal static void Shutdown() { file?.Shutdown(); file = null; } internal static bool TryGetEffects(BossPower power, int level, out IReadOnlyList<PassiveEffect> effects) { effects = PowerDefinitions.NoEffects; if (!Enabled || !table.TryGetValue(power.YamlName, out var value)) { return false; } if (value.TryGetValue(level, out var value2)) { effects = value2; } return true; } internal static bool TryGetMaxLevel(BossPower power, out int maxLevel) { maxLevel = 0; if (!Enabled || !table.TryGetValue(power.YamlName, out var value)) { return false; } if (value.Count > 0) { maxLevel = value.Keys.Max(); } return true; } private static void ReloadFromConfig() { string value = ValConfig.AdvancedPowersYaml.Value; if (string.IsNullOrWhiteSpace(value)) { table = new Dictionary<string, Dictionary<int, List<PassiveEffect>>>(); return; } Dictionary<string, Dictionary<int, List<PassiveEffect>>> dictionary = Parse(value); if (dictionary != null) { table = dictionary; } } private static Dictionary<string, Dictionary<int, List<PassiveEffect>>> Parse(string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Dictionary<string, object>> dictionary; try { dictionary = new DeserializerBuilder().Build().Deserialize<Dictionary<string, Dictionary<string, object>>>(text); } catch (Exception ex) { Logger.LogError("Powers.yaml is not valid YAML, keeping the previous power definitions: " + ex.Message); return null; } if (dictionary == null) { Logger.LogWarning("Powers.yaml is empty; the built-in features stay in use."); return new Dictionary<string, Dictionary<int, List<PassiveEffect>>>(); } Dictionary<string, Dictionary<int, List<PassiveEffect>>> dictionary2 = new Dictionary<string, Dictionary<int, List<PassiveEffect>>>(); foreach (KeyValuePair<string, Dictionary<string, object>> item in dictionary) { if (PowerDefinitions.ByYamlName(item.Key) == null) { Logger.LogWarning("Powers.yaml: unknown power '" + item.Key + "', ignored."); continue; } Dictionary<int, List<PassiveEffect>> dictionary3 = new Dictionary<int, List<PassiveEffect>>(); foreach (KeyValuePair<string, object> item2 in item.Value ?? new Dictionary<string, object>()) { if (!int.TryParse(item2.Key, out var result) || result < 1 || result > 100) { Logger.LogWarning(string.Format("{0}: {1} has level '{2}', expected 1-{3}. Ignored.", "Powers.yaml", item.Key, item2.Key, 100)); continue; } string text2 = string.Format("{0}: {1} level {2}", "Powers.yaml", item.Key, result); List<PassiveEffect> list = new List<PassiveEffect>(); if (item2.Value is Dictionary<object, object> dictionary4) { foreach (KeyValuePair<object, object> item3 in dictionary4) { string text3 = item3.Key?.ToString(); EffectCatalog.Kind kind = EffectCatalog.Find(text3); if (kind == null) { Logger.LogWarning(text2 + ": unknown effect '" + text3 + "', ignored. The header of the file lists every effect."); } else { kind.Parse(item3.Value, text2, list); } } } else if (item2.Value != null) { Logger.LogWarning(text2 + ": expected effects like 'MovementSpeed: 0.05' under the level, ignored."); } dictionary3[result] = list; } int num = ((dictionary3.Count > 0) ? dictionary3.Keys.Max() : 0); if (num == 0) { Logger.LogInfo("Powers.yaml: " + item.Key + " defines no levels; it grants nothing."); } else if (dictionary3.Count < num) { List<string> list2 = new List<string>(); for (int i = 1; i <= num; i++) { if (!dictionary3.ContainsKey(i)) { list2.Add(i.ToString()); } } Logger.LogInfo("Powers.yaml: " + item.Key + " does not define level(s) " + string.Join(", ", list2) + "; they unlock nothing new."); } dictionary2[item.Key] = dictionary3; } BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { if (!dictionary2.ContainsKey(bossPower.YamlName)) { Logger.LogWarning("Powers.yaml: no entry for '" + bossPower.YamlName + "', its built-in features stay in use."); } } return dictionary2; } private static string BuildDefaultYaml() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Progressive Powers - advanced power definitions."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Only used while 'Enable Advanced Power Config' is true in the mod's .cfg. It then replaces"); stringBuilder.AppendLine("# the 'Power N' feature entries of that file for every power listed here; a power left out"); stringBuilder.AppendLine("# keeps its entries there. Saving this file applies it live."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Layout is power -> level -> effects. A level may hold any number of effects, and"); stringBuilder.AppendLine("# the same effect may appear at several levels; numbers add up as levels unlock. A level"); stringBuilder.AppendLine("# with nothing under it unlocks nothing new. A number of 0, a resistance of Normal or a"); stringBuilder.AppendLine("# toggle of false switches that entry off."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# A power has as many levels as the highest one listed for it, from 1 up to " + 100 + ","); stringBuilder.AppendLine("# so add levels to lengthen a power or remove them to shorten it. Give every level"); stringBuilder.AppendLine("# past 7 its kill requirements in Levels.yaml too, or it unlocks for free."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# The defaults below reproduce the built-in features exactly."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Numbers are fractions of the base value unless marked (units): -0.3 is 30% less,"); stringBuilder.AppendLine("# 0.3 is 30% more."); foreach (EffectCatalog.Kind number in EffectCatalog.Numbers) { stringBuilder.AppendLine($"# {number.Key,-20}{number.Doc}"); } stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Toggles are true or false."); foreach (EffectCatalog.Kind toggle in EffectCatalog.Toggles) { stringBuilder.AppendLine($"# {toggle.Key,-20}{toggle.Doc}"); } stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Tables take one row per line, or several rows in braces, as in"); stringBuilder.AppendLine("# Resistance: { Fire: Resistant, Frost: Resistant }."); foreach (EffectCatalog.Kind table in EffectCatalog.Tables) { string text = "# " + table.Key + ": " + table.Shape; if (text.Length < 48) { stringBuilder.AppendLine(text.PadRight(48) + table.Doc); continue; } stringBuilder.AppendLine(text); stringBuilder.AppendLine("# " + table.Doc); } stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Damage types, for Resistance and DamageDealt:"); AppendWrapped(stringBuilder, EffectCatalog.DamageTargets.Select((EffectCatalog.DamageTarget t) => t.Name)); stringBuilder.AppendLine("# Melee is blunt, slash and pierce; Elemental is fire, frost, lightning, poison and"); stringBuilder.AppendLine("# spirit; All is every type. Pure (typeless damage) is for DamageDealt only."); stringBuilder.AppendLine("# Resistance modifiers:"); stringBuilder.AppendLine("# Normal, SlightlyResistant, Resistant, VeryResistant, Immune, Ignore, SlightlyWeak, Weak,"); stringBuilder.AppendLine("# VeryWeak"); stringBuilder.AppendLine("# Skills, for SkillLevel, SkillGain and AttackDamage:"); AppendWrapped(stringBuilder, (from n in Enum.GetNames(typeof(SkillType)) where n != "None" && n != "All" select n).Concat(new string[1] { "All" })); stringBuilder.AppendLine("# HealthTick heals Health every Interval seconds (10 when left out); a negative Health"); stringBuilder.AppendLine("# hurts instead. MinHealth is optional and pauses the tick while health is below that"); stringBuilder.AppendLine("# fraction of the maximum."); stringBuilder.AppendLine(); BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { stringBuilder.AppendLine(bossPower.YamlName + ":"); for (int num2 = 0; num2 < bossPower.Features.Length; num2++) { stringBuilder.AppendLine($" {num2 + 1}:"); string[] array = bossPower.Features[num2].AdvancedYaml().Split(new char[1] { '\n' }); foreach (string text2 in array) { stringBuilder.AppendLine(" " + text2); } } stringBuilder.AppendLine(); } return stringBuilder.ToString(); } private static void AppendWrapped(StringBuilder sb, IEnumerable<string> names) { StringBuilder stringBuilder = new StringBuilder("# "); foreach (string name in names) { string text = ((stringBuilder.Length > "# ".Length) ? (", " + name) : name); if (stringBuilder.Length + text.Length > 92) { sb.AppendLine(stringBuilder.Append(',').ToString()); stringBuilder.Clear().Append("# ").Append(name); } else { stringBuilder.Append(text); } } sb.AppendLine(stringBuilder.ToString()); } } internal static class Attunements { internal const string CustomDataKey = "ProgressivePowers.Attuned"; internal const int MaxSlots = 8; private static readonly List<BossPower> stored = new List<BossPower>(); private static string storedRaw = ""; internal static int Max { get { if (ValConfig.MaxAttunedPowers != null) { return Mathf.Clamp(ValConfig.MaxAttunedPowers.Value, 1, 8); } return 1; } } internal static List<BossPower> Active(Player player) { List<BossPower> list = Stored(player); int num = FirstActive(list); return list.GetRange(num, list.Count - num); } internal static bool IsAttuned(Player player, BossPower power) { List<BossPower> list = Stored(player); int num = list.IndexOf(power); if (num >= 0) { return num >= FirstActive(list); } return false; } internal static BossPower Displaced(Player player) { List<BossPower> list = Stored(player); if (list.Count < Max) { return null; } return list[FirstActive(list)]; } internal static void Sync(Player player) { BossPower bossPower = PowerDefinitions.ByPowerName(player.GetGuardianPowerName()); if (bossPower != null) { List<BossPower> list = Stored(player); if (list.Count <= 0 || list[list.Count - 1] != bossPower) { list.Remove(bossPower); list.Add(bossPower); DropSetAside(list); Write(player); Logger.LogDebug("Attuned to " + bossPower.YamlName + "; attunements are now " + storedRaw + "."); } } } internal static void Release(Player player, BossPower power) { if (IsAttuned(player, power)) { List<BossPower> list = Stored(player); DropSetAside(list); list.Remove(power); Write(player); Logger.LogDebug("Released " + power.YamlName + "; attunements are now " + ((storedRaw.Length > 0) ? storedRaw : "empty") + "."); if (player.GetGuardianPowerName() == power.PowerName) { player.SetGuardianPower((list.Count > 0) ? list[list.Count - 1].PowerName : ""); } else { PassivePowerManager.Refresh(force: true); } } } private static int FirstActive(List<BossPower> all) { return Math.Max(0, all.Count - Max); } private static void DropSetAside(List<BossPower> all) { all.RemoveRange(0, FirstActive(all)); } private static List<BossPower> Stored(Player player) { if (!player.m_customData.TryGetValue("ProgressivePowers.Attuned", out var value) || value == null) { value = ""; } if (value == storedRaw) { return stored; } stored.Clear(); storedRaw = value; string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { BossPower bossPower = PowerDefinitions.ByYamlName(text); if (bossPower == null) { Logger.LogWarning("Ignoring unknown power '" + text + "' in stored attunements."); } else if (!stored.Contains(bossPower)) { stored.Add(bossPower); } } } return stored; } private static void Write(Player player) { storedRaw = string.Join(",", stored.Select((BossPower p) => p.YamlName)); if (storedRaw.Length == 0) { player.m_customData.Remove("ProgressivePowers.Attuned"); } else { player.m_customData["ProgressivePowers.Attuned"] = storedRaw; } } } internal static class KillCommands { internal abstract class KillCommand : ConsoleCommand { public override bool IsNetwork => true; public override List<string> CommandOptionList() { return BossOptions(); } public override void Run(string[] args, Terminal context) { output = context; args = args.Where((string a) => a.Length > 0).ToArray(); if (args.Length == 0) { Print("Usage: " + ((ConsoleCommand)this).Name + " " + ((ConsoleCommand)this).Help); return; } string text = NormalizeBoss(args[0]); if (text == null) { Print("Unknown boss '" + args[0] + "'. Use one of: " + string.Join(", ", BossOptions()) + "."); } else { RunForBoss(text, args.Skip(1).ToArray()); } } protected abstract void RunForBoss(string boss, string[] rest); } internal sealed class GiveCommand : KillCommand { public override string Name => "pp_givekills"; public override string Help => "<boss|all> [count] [player] - admin only: credits a player with boss kills. Count defaults to 1, player to yourself."; protected override void RunForBoss(string boss, string[] rest) { int num = 1; int count = 0; if (rest.Length != 0 && int.TryParse(rest[0], out var result)) { num = result; count = 1; } if (num < 1 || num > 1000) { Print($"Count must be between 1 and {1000}."); return; } string text = string.Join(" ", rest.Skip(count)); if (ZNet.instance.IsServer()) { DispatchGive(ZNet.GetUID(), HostName(), boss, num, text); return; } ZRpc obj = ServerRpc(); if (obj != null) { obj.Invoke("MidnightsFX.ProgressivePowers.GiveKillsRequest", new object[3] { boss, num, text }); } } } internal sealed class ResetCommand : KillCommand { public override string Name => "pp_resetkills"; public override string Help => "<boss|all> [player] - admin only: sets a player's boss kills back to zero. Player defaults to yourself."; protected override void RunForBoss(string boss, string[] rest) { string text = string.Join(" ", rest); if (ZNet.instance.IsServer()) { DispatchReset(ZNet.GetUID(), HostName(), boss, text); return; } ZRpc obj = ServerRpc(); if (obj != null) { obj.Invoke("MidnightsFX.ProgressivePowers.ResetKillsRequest", new object[2] { boss, text }); } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class RegisterRpcs { private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { peer.m_rpc.Register<string, int, string>("MidnightsFX.ProgressivePowers.GiveKillsRequest", (Action<ZRpc, string, int, string>)OnGiveRequest); peer.m_rpc.Register<string, string>("MidnightsFX.ProgressivePowers.ResetKillsRequest", (Action<ZRpc, string, string>)OnResetRequest); peer.m_rpc.Register<long, string>("MidnightsFX.ProgressivePowers.GiveKillsResult", (Action<ZRpc, long, string>)OnResult); } else { peer.m_rpc.Register<string, int, long>("MidnightsFX.ProgressivePowers.GiveKills", (Action<ZRpc, string, int, long>)OnGive); peer.m_rpc.Register<string, long>("MidnightsFX.ProgressivePowers.ResetKills", (Action<ZRpc, string, long>)OnReset); peer.m_rpc.Register<string>("MidnightsFX.ProgressivePowers.GiveKillsReply", (Action<ZRpc, string>)OnReply); } } } internal const string GiveName = "pp_givekills"; internal const string ResetName = "pp_resetkills"; private const string AllBosses = "all"; private const int MaxCount = 1000; private const string GiveRequestRpc = "MidnightsFX.ProgressivePowers.GiveKillsRequest"; private const string GiveRpc = "MidnightsFX.ProgressivePowers.GiveKills"; private const string ResetRequestRpc = "MidnightsFX.ProgressivePowers.ResetKillsRequest"; private const string ResetRpc = "MidnightsFX.ProgressivePowers.ResetKills"; private const string ResultRpc = "MidnightsFX.ProgressivePowers.GiveKillsResult"; private const string ReplyRpc = "MidnightsFX.ProgressivePowers.GiveKillsReply"; private static Terminal output; private static ZRpc ServerRpc() { if (!ModCompatibility.IsModuleOnServer("MidnightsFX.ProgressivePowers")) { Print("The server is not running ProgressivePowers, so it cannot check that you are an admin. This command needs the mod on the server."); return null; } ZRpc serverRPC = ZNet.instance.GetServerRPC(); if (serverRPC == null) { Print("Not connected to a server."); } return serverRPC; } private static void OnGiveRequest(ZRpc rpc, string boss, int count, string target) { ZNetPeer val = AdminRequester(rpc, "give boss kills"); if (val != null) { boss = NormalizeBoss(boss); if (boss == null || count < 1 || count > 1000) { rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsReply", new object[1] { "Invalid boss or count." }); } else { DispatchGive(val.m_uid, val.m_playerName, boss, count, target); } } } private static void OnResetRequest(ZRpc rpc, string boss, string target) { ZNetPeer val = AdminRequester(rpc, "reset boss kills"); if (val != null) { boss = NormalizeBoss(boss); if (boss == null) { rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsReply", new object[1] { "Invalid boss." }); } else { DispatchReset(val.m_uid, val.m_playerName, boss, target); } } } private static ZNetPeer AdminRequester(ZRpc rpc, string action) { ZNetPeer peer = ZNet.instance.GetPeer(rpc); if (peer == null) { return null; } if (!ZNet.instance.IsAdmin(rpc.GetSocket().GetHostName())) { Logger.LogWarning(peer.m_playerName + " tried to " + action + " but is not an admin."); rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsReply", new object[1] { "Only admins can " + action + "." }); return null; } return peer; } private static void DispatchGive(long requester, string requesterName, string boss, int count, string target) { if (FindTarget(requester, requesterName, target, out var peer, out var targetName)) { Logger.LogInfo($"{requesterName} gave {targetName} {count} kill(s) of {Describe(boss)}."); if (peer == null) { Reply(requester, Summarize(targetName, Give(boss, count))); return; } peer.m_rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKills", new object[3] { boss, count, requester }); } } private static void DispatchReset(long requester, string requesterName, string boss, string target) { if (FindTarget(requester, requesterName, target, out var peer, out var targetName)) { Logger.LogInfo(requesterName + " reset " + targetName + "'s kills of " + Describe(boss) + "."); if (peer == null) { Reply(requester, Summarize(targetName, Reset(boss))); return; } peer.m_rpc.Invoke("MidnightsFX.ProgressivePowers.ResetKills", new object[2] { boss, requester }); } } private static bool FindTarget(long requester, string requesterName, string target, out ZNetPeer peer, out string targetName) { peer = null; targetName = requesterName; if (string.IsNullOrEmpty(target)) { if (requester == ZNet.GetUID()) { return true; } peer = ZNet.instance.GetPeer(requester); return peer != null; } List<ZNetPeer> list = (from p in ZNet.instance.GetPeers() where p.IsReady() && SameName(p.m_playerName, target) select p).ToList(); bool flag = !ZNet.instance.IsDedicated() && SameName(HostName(), target); int num = list.Count + (flag ? 1 : 0); if (num == 0) { Reply(requester, "No player named '" + target + "' is online."); return false; } if (num > 1) { Reply(requester, "More than one player online is named '" + target + "'."); return false; } peer = (flag ? null : list[0]); targetName = (flag ? HostName() : peer.m_playerName); return true; } private static void OnGive(ZRpc rpc, string boss, int count, long requester) { boss = NormalizeBoss(boss); if (boss != null) { rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsResult", new object[2] { requester, Give(boss, count) ?? "" }); } } private static void OnReset(ZRpc rpc, string boss, long requester) { boss = NormalizeBoss(boss); if (boss != null) { rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsResult", new object[2] { requester, Reset(boss) ?? "" }); } } private static void OnResult(ZRpc rpc, long requester, string totals) { ZNetPeer peer = ZNet.instance.GetPeer(rpc); if (peer != null) { Reply(requester, Summarize(peer.m_playerName, totals)); } } private static void OnReply(ZRpc rpc, string message) { Print(message); } private static string Give(string boss, int count) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } BossPower[] array = Powers(boss); BossPower[] array2 = array; foreach (BossPower power in array2) { KillTracker.RecordKill(localPlayer, power, count); } PassivePowerManager.Refresh(force: true); Localization loc = Localization.instance; string newValue = string.Join(", ", array.Select((BossPower p) => $"{loc.Localize(p.LocKey)} x{count}")); ((Character)localPlayer).Message((MessageType)2, loc.Localize("$pp_kills_granted").Replace("$1", newValue), 0, (Sprite)null, false); return Totals(array); } private static string Reset(string boss) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } BossPower[] array = Powers(boss); BossPower[] array2 = array; foreach (BossPower power in array2) { KillTracker.ResetKills(localPlayer, power); } PassivePowerManager.Refresh(force: true); Localization loc = Localization.instance; string newValue = string.Join(", ", array.Select((BossPower p) => loc.Localize(p.LocKey))); ((Character)localPlayer).Message((MessageType)2, loc.Localize("$pp_kills_reset").Replace("$1", newValue), 0, (Sprite)null, false); return Totals(array); } private static BossPower[] Powers(string boss) { if (!(boss == "all")) { return new BossPower[1] { PowerDefinitions.ByYamlName(boss) }; } return PowerDefinitions.All; } private static string Totals(BossPower[] powers) { return string.Join(", ", powers.Select((BossPower p) => $"{p.YamlName} x{KillTracker.Get(p.YamlName)}")); } private static string Summarize(string name, string totals) { if (!string.IsNullOrEmpty(totals)) { return name + " now has " + totals + "."; } return name + " has no character loaded right now (dead or still loading), so nothing changed. Try again in a moment."; } private static string Describe(string boss) { if (!(boss == "all")) { return boss; } return "every boss"; } private static void Reply(long requester, string message) { if (requester == ZNet.GetUID()) { Print(message); return; } ZNetPeer peer = ZNet.instance.GetPeer(requester); if (peer != null) { peer.m_rpc.Invoke("MidnightsFX.ProgressivePowers.GiveKillsReply", new object[1] { message }); } } private static void Print(string message) { Terminal val = (Terminal)(((Object)(object)output != (Object)null) ? ((object)output) : ((object)Console.instance)); if ((Object)(object)val != (Object)null) { val.AddString(message); } } private static string NormalizeBoss(string arg) { if (string.Equals(arg, "all", StringComparison.OrdinalIgnoreCase)) { return "all"; } return PowerDefinitions.All.FirstOrDefault((BossPower p) => string.Equals(p.YamlName, arg, StringComparison.OrdinalIgnoreCase))?.YamlName; } private static List<string> BossOptions() { return PowerDefinitions.All.Select((BossPower p) => p.YamlName).Append("all").ToList(); } private static bool SameName(string a, string b) { return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } private static string HostName() { Game instance = Game.instance; object obj; if (instance == null) { obj = null; } else { PlayerProfile playerProfile = instance.GetPlayerProfile(); obj = ((playerProfile != null) ? playerProfile.GetName() : null); } if (obj == null) { obj = ""; } return (string)obj; } } internal static class KillTracker { internal const string CustomDataKey = "ProgressivePowers.Kills"; private static readonly Dictionary<string, int> counts = new Dictionary<string, int>(); private static Player boundPlayer; private static bool loaded; private static bool reportedFresh; internal static IEnumerable<KeyValuePair<string, int>> All => counts; internal static int Get(string yamlName) { if (!counts.TryGetValue(yamlName, out var value)) { return 0; } return value; } internal static void Bind(Player player) { if (!((Object)(object)player == (Object)null)) { if ((Object)(object)boundPlayer != (Object)(object)player) { reportedFresh = false; } boundPlayer = player; counts.Clear(); loaded = false; if (player.m_customData.TryGetValue("ProgressivePowers.Kills", out var value) && !string.IsNullOrEmpty(value)) { Parse(value); loaded = true; Logger.LogDebug("Loaded boss kills for " + player.GetPlayerName() + ": " + Serialize()); } else { Migrate(player); } } } internal static void EnsureLoaded(Player player) { if (!((Object)(object)player == (Object)null) && (!((Object)(object)boundPlayer == (Object)(object)player) || !loaded)) { Bind(player); } } private static void Migrate(Player player) { BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { if (((Humanoid)player).HaveUniqueKey(bossPower.BossKey)) { counts[bossPower.YamlName] = 1; } } if (counts.Count > 0) { loaded = true; Save(player); Logger.LogInfo("No kill record for " + player.GetPlayerName() + "; seeded from defeated_* keys: " + Serialize()); } else if (!reportedFresh) { reportedFresh = true; Logger.LogDebug("No kill record for " + player.GetPlayerName() + " and no defeated_* keys yet; retrying until they load."); } } internal static void RecordKill(Player player, BossPower power, int count = 1) { EnsureLoaded(player); counts.TryGetValue(power.YamlName, out var value); counts[power.YamlName] = value + count; boundPlayer = player; loaded = true; Save(player); Logger.LogInfo((count == 1) ? $"Recorded {power.YamlName} kill #{value + 1}." : $"Recorded {count} {power.YamlName} kills, {value + count} in total."); } internal static void ResetKills(Player player, BossPower power) { EnsureLoaded(player); counts.TryGetValue(power.YamlName, out var value); counts[power.YamlName] = 0; boundPlayer = player; loaded = true; Save(player); Logger.LogInfo($"Reset {power.YamlName} kills from {value} to 0."); } internal static void Flush(Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)boundPlayer != (Object)(object)player) && loaded) { Save(player); } } private static void Save(Player player) { if (!((Object)(object)player == (Object)null)) { player.m_customData["ProgressivePowers.Kills"] = Serialize(); } } private static string Serialize() { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, int> count in counts) { if (count.Value >= 0) { if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(count.Key).Append(':').Append(count.Value); } } return stringBuilder.ToString(); } private static void Parse(string raw) { string[] array = raw.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ':' }); if (array2.Length == 2) { string text = array2[0].Trim(); int result; if (PowerDefinitions.ByYamlName(text) == null) { Logger.LogWarning("Ignoring unknown boss '" + text + "' in stored kill counts."); } else if (int.TryParse(array2[1].Trim(), out result) && result >= 0) { counts[text] = result; } } } } } internal static class LevelRequirements { private const string FileName = "Levels.yaml"; private const string LegacyFileName = "ProgressivePowers.Levels.yaml"; private static Dictionary<string, Dictionary<int, Dictionary<string, int>>> table = new Dictionary<string, Dictionary<int, Dictionary<string, int>>>(); private static SyncedConfigFile file; private static bool warnedOverrideNeedsCheats; internal static void Initialize() { file = new SyncedConfigFile("Levels.yaml", ValConfig.LevelRequirementsYaml, BuildDefaultYaml, "ProgressivePowers.Levels.yaml"); try { file.Initialize(); ReloadFromConfig(); ReportUncoveredLevels(); } catch (Exception ex) { Logger.LogError("Could not initialize level requirements, falling back to defaults: " + ex.Message); table = BuildDefaultTable(); } ValConfig.DebugLevelOverride.SettingChanged += delegate { warnedOverrideNeedsCheats = false; }; ValConfig.LevelRequirementsYaml.SettingChanged += delegate { ConfigChangeDebouncer.Schedule("LevelRequirements", delegate { ReloadFromConfig(); QueueCoverageReport(); ValConfig.MarkChanged(); PassivePowerManager.Refresh(force: true); }); }; ValConfig.EnableAdvancedPowers.SettingChanged += delegate { QueueCoverageReport(); }; } internal static void Shutdown() { file?.Shutdown(); file = null; } private static void ReloadFromConfig() { string value = ValConfig.LevelRequirementsYaml.Value; if (string.IsNullOrWhiteSpace(value)) { table = BuildDefaultTable(); return; } Dictionary<string, Dictionary<int, Dictionary<string, int>>> dictionary = Parse(value); if (dictionary != null) { table = dictionary; } } private static Dictionary<string, Dictionary<int, Dictionary<string, int>>> Parse(string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary; try { dictionary = new DeserializerBuilder().Build().Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(text); } catch (Exception ex) { Logger.LogError("Levels.yaml is not valid YAML, keeping the previous requirements: " + ex.Message); return null; } if (dictionary == null) { Logger.LogWarning("Levels.yaml is empty, using defaults."); return BuildDefaultTable(); } Dictionary<string, Dictionary<int, Dictionary<string, int>>> dictionary2 = new Dictionary<string, Dictionary<int, Dictionary<string, int>>>(); foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> item in dictionary) { if (PowerDefinitions.ByYamlName(item.Key) == null) { Logger.LogWarning("Levels.yaml: unknown power '" + item.Key + "', ignored."); continue; } Dictionary<int, Dictionary<string, int>> dictionary3 = new Dictionary<int, Dictionary<string, int>>(); foreach (KeyValuePair<string, Dictionary<string, string>> item2 in item.Value ?? new Dictionary<string, Dictionary<string, string>>()) { if (!int.TryParse(item2.Key, out var result) || result < 1 || result > 100) { Logger.LogWarning(string.Format("{0}: {1} has level '{2}', expected 1-{3}. Ignored.", "Levels.yaml", item.Key, item2.Key, 100)); continue; } Dictionary<string, int> dictionary4 = new Dictionary<string, int>(); foreach (KeyValuePair<string, string> item3 in item2.Value ?? new Dictionary<string, string>()) { int result2; if (PowerDefinitions.ByYamlName(item3.Key) == null) { Logger.LogWarning(string.Format("{0}: {1} level {2} requires unknown boss '{3}', ignored.", "Levels.yaml", item.Key, result, item3.Key)); } else if (!int.TryParse(item3.Value, out result2) || result2 < 0) { Logger.LogWarning(string.Format("{0}: {1} level {2} has a bad kill count for '{3}', ignored.", "Levels.yaml", item.Key, result, item3.Key)); } else if (result2 > 0) { dictionary4[item3.Key] = result2; } } dictionary3[result] = dictionary4; } dictionary2[item.Key] = dictionary3; } Dictionary<string, Dictionary<int, Dictionary<string, int>>> dictionary5 = null; BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { if (!dictionary2.ContainsKey(bossPower.YamlName)) { if (dictionary5 == null) { dictionary5 = BuildDefaultTable(); } dictionary2[bossPower.YamlName] = dictionary5[bossPower.YamlName]; Logger.LogWarning("Levels.yaml: no entry for '" + bossPower.YamlName + "', using defaults for it."); } } return dictionary2; } private static void ReportUncoveredLevels() { BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { table.TryGetValue(bossPower.YamlName, out var value); List<string> list = new List<string>(); int num = PowerDefinitions.MaxLevel(bossPower); for (int j = 1; j <= num; j++) { if (value == null || !value.ContainsKey(j)) { list.Add(j.ToString()); } } if (list.Count > 0) { Logger.LogWarning("Levels.yaml: " + bossPower.YamlName + " does not define level(s) " + string.Join(", ", list) + "; they will unlock for free once the levels below them are met."); } } } internal static void QueueCoverageReport() { ConfigChangeDebouncer.Schedule("ReportUncoveredLevels", ReportUncoveredLevels); } internal static Dictionary<string, int> CumulativeRequirement(BossPower power, int level) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (!table.TryGetValue(power.YamlName, out var value)) { return dictionary; } for (int i = 1; i <= level; i++) { if (!value.TryGetValue(i, out var value2)) { continue; } foreach (KeyValuePair<string, int> item in value2) { if (!dictionary.TryGetValue(item.Key, out var value3) || item.Value > value3) { dictionary[item.Key] = item.Value; } } } return dictionary; } internal static Dictionary<string, int> Outstanding(BossPower power, int level) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<string, int> item in CumulativeRequirement(power, level)) { int num = item.Value - KillTracker.Get(item.Key); if (num > 0) { dictionary[item.Key] = num; } } return dictionary; } internal static int GetLevel(BossPower power) { int value = ValConfig.DebugLevelOverride.Value; if (value >= 0) { if ((Object)(object)Console.instance != (Object)null && ((Terminal)Console.instance).IsCheatsEnabled()) { return Math.Min(value, PowerDefinitions.MaxLevel(power)); } if (!warnedOverrideNeedsCheats) { warnedOverrideNeedsCheats = true; Logger.LogWarning("Debug Level Override is set but ignored: it requires 'devcommands' in the console, on a world you host."); } } int result = 0; int num = PowerDefinitions.MaxLevel(power); for (int i = 1; i <= num && Outstanding(power, i).Count <= 0; i++) { result = i; } return result; } private static List<KeyValuePair<string, int>> DefaultLevels(int powerIndex, int levelCount) { BossPower[] all = PowerDefinitions.All; string yamlName = all[powerIndex].YamlName; string yamlName2 = all[^1].YamlName; Dictionary<string, int> dictionary = new Dictionary<string, int>(); List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); int num = powerIndex; bool flag = true; for (int i = 1; i <= levelCount; i++) { string key; int value; if (num < all.Length) { key = all[num].YamlName; value = 1; num++; } else { key = ((yamlName == yamlName2) ? yamlName : (flag ? yamlName : yamlName2)); flag = !flag; dictionary.TryGetValue(key, out var value2); value = value2 + 1; } dictionary[key] = value; list.Add(new KeyValuePair<string, int>(key, value)); } return list; } private static Dictionary<string, Dictionary<int, Dictionary<string, int>>> BuildDefaultTable() { Dictionary<string, Dictionary<int, Dictionary<string, int>>> dictionary = new Dictionary<string, Dictionary<int, Dictionary<string, int>>>(); for (int i = 0; i < PowerDefinitions.All.Length; i++) { Dictionary<int, Dictionary<string, int>> dictionary2 = new Dictionary<int, Dictionary<string, int>>(); List<KeyValuePair<string, int>> list = DefaultLevels(i, 100); for (int j = 0; j < list.Count; j++) { dictionary2[j + 1] = new Dictionary<string, int> { { list[j].Key, list[j].Value } }; } dictionary[PowerDefinitions.All[i].YamlName] = dictionary2; } return dictionary; } private static string BuildDefaultYaml() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Progressive Powers - mastery level requirements."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Each power has 7 levels, or as many as Powers.yaml gives it while the advanced power"); stringBuilder.AppendLine("# config is on; a level without an entry here unlocks for free. A level lists boss kill"); stringBuilder.AppendLine("# counts; ALL of them must be met, and levels are cumulative - reaching level 3 also"); stringBuilder.AppendLine("# requires levels 1 and 2. That is why each level below only names what it adds."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Counts are repeat kills by your own character, so you can mix them freely:"); stringBuilder.AppendLine("# 4: { Eikthyr: 3, TheElder: 2 }"); stringBuilder.AppendLine("# means level 4 needs three Eikthyr kills and two Elder kills."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Valid boss names: " + string.Join(", ", Array.ConvertAll(PowerDefinitions.All, (BossPower p) => p.YamlName))); stringBuilder.AppendLine(); for (int num = 0; num < PowerDefinitions.All.Length; num++) { stringBuilder.AppendLine(PowerDefinitions.All[num].YamlName + ":"); List<KeyValuePair<string, int>> list = DefaultLevels(num, PowerDefinitions.All[num].Features.Length); for (int num2 = 0; num2 < list.Count; num2++) { stringBuilder.AppendLine($" {num2 + 1}: {{ {list[num2].Key}: {list[num2].Value} }}"); } stringBuilder.AppendLine(); } return stringBuilder.ToString(); } } internal static class PassivePowerManager { private static bool built; private static string lastSignature; internal static IReadOnlyList<BossPower> Attuned { get; private set; } = new BossPower[0]; internal static IReadOnlyList<int> Levels { get; private set; } = new int[0]; internal static IReadOnlyList<int> MaxLevels { get; private set; } = new int[0]; internal static void BuildPassiveEffects() { if (built) { return; } if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ObjectDB.instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("GP_Eikthyr")) == (Object)null) { Logger.LogDebug("ObjectDB is not ready; passives will be built on a later attempt."); return; } BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { bossPower.PowerHash = StringExtensionMethods.GetStableHashCode(bossPower.PowerName); bossPower.PassiveHash = StringExtensionMethods.GetStableHashCode(bossPower.PassiveName); StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(bossPower.PowerHash); if ((Object)(object)statusEffect == (Object)null) { Logger.LogWarning("Vanilla status effect '" + bossPower.PowerName + "' was not found in ObjectDB; '" + bossPower.PassiveName + "' will have no icon."); } SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>(); ((Object)val).name = bossPower.PassiveName; ((Object)val).hideFlags = (HideFlags)61; ((StatusEffect)val).m_name = (((Object)(object)statusEffect != (Object)null) ? statusEffect.m_name : bossPower.LocKey); ((StatusEffect)val).m_icon = (((Object)(object)statusEffect != (Object)null) ? statusEffect.m_icon : null); ((StatusEffect)val).m_category = ""; ((StatusEffect)val).m_startMessage = ""; ((StatusEffect)val).m_stopMessage = ""; ((StatusEffect)val).m_repeatMessage = ""; ((StatusEffect)val).m_repeatInterval = 0f; bossPower.PassiveSE = val; Neutralize(val); } built = true; Logger.LogInfo($"Built {PowerDefinitions.All.Length} passive power effects."); Refresh(force: true); } internal static void Refresh(bool force = false) { if (!built) { BuildPassiveEffects(); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !built) { lastSignature = null; return; } KillTracker.EnsureLoaded(localPlayer); Attunements.Sync(localPlayer); List<BossPower> list = Attunements.Active(localPlayer); list.Reverse(); int[] levels = list.Select(LevelRequirements.GetLevel).ToArray(); int[] array = list.Select(PowerDefinitions.MaxLevel).ToArray(); Attuned = list; Levels = levels; MaxLevels = array; SEMan sEMan = ((Character)localPlayer).GetSEMan(); string text = string.Join(",", list.Select((BossPower p, int i) => $"{p.PassiveName}:{levels[i]}")) + "|" + ValConfig.Generation; if (!force && text == lastSignature && StateMatches(sEMan, list, levels)) { return; } lastSignature = text; BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { if (bossPower.PassiveHash != 0) { sEMan.RemoveStatusEffect(bossPower.PassiveHash, true); } } if (list.Count == 0) { Logger.LogDebug("No power selected."); return; } for (int num2 = 0; num2 < list.Count; num2++) { BossPower bossPower2 = list[num2]; if (levels[num2] <= 0) { Logger.LogDebug(bossPower2.YamlName + " is at level 0; no passive applied."); continue; } BuildPassive(bossPower2, levels[num2]); sEMan.AddStatusEffect((StatusEffect)(object)bossPower2.PassiveSE, true, 0, 0f, (short)(-1)); Logger.LogDebug($"Applied {bossPower2.PassiveName} at level {levels[num2]}/{array[num2]}."); } } private static bool StateMatches(SEMan seman, List<BossPower> attuned, int[] levels) { BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { int num = attuned.IndexOf(bossPower); bool flag = num >= 0 && levels[num] > 0; if (seman.HaveStatusEffect(bossPower.PassiveHash) != flag) { return false; } } return true; } internal static void BuildPassive(BossPower power, int level) { SE_Stats passiveSE = power.PassiveSE; Neutralize(passiveSE); PassiveBuild build = new PassiveBuild(passiveSE); for (int i = 1; i <= level; i++) { foreach (PassiveEffect item in PowerDefinitions.EffectsAt(power, i)) { if (item.Enabled) { item.Apply(build); } } } if (!ValConfig.ShowPassiveInStatusBar.Value) { ((StatusEffect)passiveSE).m_icon = null; } else { RestoreIcon(power); } } private static void RestoreIcon(BossPower power) { if (!((Object)(object)((StatusEffect)power.PassiveSE).m_icon != (Object)null) && !((Object)(object)ObjectDB.instance == (Object)null)) { StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(power.PowerHash); if ((Object)(object)statusEffect != (Object)null) { ((StatusEffect)power.PassiveSE).m_icon = statusEffect.m_icon; } } } private static void Neutralize(SE_Stats se) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) ((StatusEffect)se).m_ttl = 0f; ((StatusEffect)se).m_cooldown = 0f; se.m_runStaminaDrainModifier = 0f; se.m_jumpStaminaUseModifier = 0f; se.m_swimStaminaUseModifier = 0f; se.m_dodgeStaminaUseModifier = 0f; se.m_attackStaminaUseModifier = 0f; se.m_blockStaminaUseModifier = 0f; se.m_blockStaminaUseFlatValue = 0f; se.m_sneakStaminaUseModifier = 0f; se.m_homeItemStaminaUseModifier = 0f; se.m_speedModifier = 0f; se.m_swimSpeedModifier = 0f; se.m_jumpModifier = Vector3.zero; se.m_windMovementModifier = 0f; se.m_windRunStaminaModifier = 0f; se.m_fallDamageModifier = 0f; se.m_maxMaxFallSpeed = 0f; se.m_healthRegenMultiplier = 1f; se.m_staminaRegenMultiplier = 1f; se.m_eitrRegenMultiplier = 1f; se.m_addArmor = 0f; se.m_armorMultiplier = 0f; se.m_addMaxCarryWeight = 0f; se.m_noiseModifier = 0f; se.m_stealthModifier = 0f; se.m_adrenalineModifier = 0f; se.m_staggerModifier = 0f; se.m_timedBlockBonus = 0f; se.m_raiseSkill = (SkillType)0; se.m_raiseSkillModifier = 0f; se.m_skillLevel = (SkillType)0; se.m_skillLevelModifier = 0f; se.m_skillLevel2 = (SkillType)0; se.m_skillLevelModifier2 = 0f; se.m_tickInterval = 0f; se.m_healthPerTick = 0f; se.m_healthPerTickMinHealthPercentage = 0f; se.m_mods = new List<DamageModPair>(); se.m_percentigeDamageModifiers = default(DamageTypes); se.m_modifyAttackSkill = (SkillType)0; se.m_damageModifier = 1f; ((StatusEffect)se).m_attributes = (StatusAttribute)0; } } internal sealed class BossPower { internal string PowerName; internal string PassiveName; internal string BossKey; internal string YamlName; internal string LocKey; internal string ConfigCategory; internal PowerFeature[] Features; internal int PowerHash; internal int PassiveHash; internal SE_Stats PassiveSE; internal const int LevelLimit = 100; private IReadOnlyList<PassiveEffect>[] builtInLevels; internal IReadOnlyList<PassiveEffect> BuiltInEffectsAt(int level) { if (builtInLevels == null) { builtInLevels = Array.ConvertAll(Features, (Converter<PowerFeature, IReadOnlyList<PassiveEffect>>)((PowerFeature f) => new PassiveEffect[1] { f })); } int num = level - 1; if (num < 0 || num >= builtInLevels.Length) { return PowerDefinitions.NoEffects; } return builtInLevels[num]; } } internal static class PowerDefinitions { internal static readonly PassiveEffect[] NoEffects = new PassiveEffect[0]; internal static readonly BossPower[] All = Build(); private static Dictionary<string, BossPower> byPowerName; private static Dictionary<string, BossPower> byYamlName; private const float PctMin = -1f; private const float PctMax = 5f; internal static BossPower ByPowerName(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (byPowerName == null) { byPowerName = All.ToDictionary((BossPower p) => p.PowerName); } if (!byPowerName.TryGetValue(name, out var value)) { return null; } return value; } internal static BossPower ByYamlName(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (byYamlName == null) { byYamlName = All.ToDictionary((BossPower p) => p.YamlName); } if (!byYamlName.TryGetValue(name, out var value)) { return null; } return value; } internal static BossPower ByBossKey(string key) { if (string.IsNullOrEmpty(key)) { return null; } BossPower[] all = All; foreach (BossPower bossPower in all) { if (string.Equals(bossPower.BossKey, key, StringComparison.OrdinalIgnoreCase)) { return bossPower; } } return null; } internal static IReadOnlyList<PassiveEffect> EffectsAt(BossPower power, int level) { if (AdvancedPowers.TryGetEffects(power, level, out var effects)) { return effects; } return power.BuiltInEffectsAt(level); } internal static int MaxLevel(BossPower power) { if (AdvancedPowers.TryGetMaxLevel(power, out var maxLevel)) { return maxLevel; } return power.Features.Length; } internal static void BindConfigs() { BossPower[] all = All; foreach (BossPower bossPower in all) { PowerFeature[] features = bossPower.Features; for (int j = 0; j < features.Length; j++) { features[j].Bind(bossPower.ConfigCategory); } } } private static BossPower[] Build() { return new BossPower[7] { new BossPower { PowerName = "GP_Eikthyr", PassiveName = "PP_Eikthyr", BossKey = "defeated_eikthyr", YamlName = "Eikthyr", LocKey = "$pp_power_eikthyr", ConfigCategory = "Power 1 - Eikthyr", Features = new PowerFeature[7] { new FloatFeature("$pp_feature_eikthyr_1", "Feature 1 - Run stamina cost", "Fraction by which running stamina drain changes. Negative is cheaper.", -0.3f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_runStaminaDrainModifier = v; }, "RunStaminaCost: {v}"), new FloatFeature("$pp_feature_eikthyr_2", "Feature 2 - Jump stamina cost", "Fraction by which jump stamina cost changes. Negative is cheaper.", -0.3f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_jumpStaminaUseModifier = v; }, "JumpStaminaCost: {v}"), new FloatFeature("$pp_feature_eikthyr_3", "Feature 3 - Swim stamina cost", "Fraction by which swim stamina cost changes. Negative is cheaper.", -0.3f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_swimStaminaUseModifier = v; }, "SwimStaminaCost: {v}"), new FloatFeature("$pp_feature_eikthyr_4", "Feature 4 - Movement speed", "Fraction added to movement speed.", 0.03f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_speedModifier = v; }, "MovementSpeed: {v}"), new FloatFeature("$pp_feature_eikthyr_5", "Feature 5 - Stamina regeneration", "Fraction added to stamina regeneration.", 0.15f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_staminaRegenMultiplier = 1f + v; }, "StaminaRegen: {v}"), new FloatFeature("$pp_feature_eikthyr_6", "Feature 6 - Fall damage", "Fraction by which fall damage changes. Negative takes less.", -0.35f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_fallDamageModifier = v; }, "FallDamage: {v}"), new FloatFeature("$pp_feature_eikthyr_7", "Feature 7 - Dodge stamina cost", "Fraction by which dodge stamina cost changes. Negative is cheaper.", -0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_dodgeStaminaUseModifier = v; }, "DodgeStaminaCost: {v}") } }, new BossPower { PowerName = "GP_TheElder", PassiveName = "PP_TheElder", BossKey = "defeated_gdking", YamlName = "TheElder", LocKey = "$pp_power_theelder", ConfigCategory = "Power 2 - The Elder", Features = new PowerFeature[7] { new FloatFeature("$pp_feature_theelder_1", "Feature 1 - Health regeneration", "Fraction added to health regeneration.", 0.15f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_healthRegenMultiplier = 1f + v; }, "HealthRegen: {v}"), new FloatFeature("$pp_feature_theelder_2", "Feature 2 - Chopping damage", "Fraction added to chop damage against trees.", 0.3f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_percentigeDamageModifiers.m_chop += v; }, "DamageDealt: { Chop: {v} }"), new FloatFeature("$pp_feature_theelder_3", "Feature 3 - Pickaxe damage", "Fraction added to pickaxe damage against rock and ore.", 0.3f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_percentigeDamageModifiers.m_pickaxe += v; }, "DamageDealt: { Pickaxe: {v} }"), new FloatFeature("$pp_feature_theelder_4", "Feature 4 - Carry weight", "Extra carry weight in units.", 75f, 0f, 1000f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { SE_Stats se = b.Se; se.m_addMaxCarryWeight += v; }, "CarryWeight: {v}"), new FloatFeature("$pp_feature_theelder_5", "Feature 5 - Woodcutting skill", "Effective Woodcutting skill levels added.", 12f, 0f, 100f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddSkillLevel((SkillType)13, v); }, "SkillLevel: { WoodCutting: {v} }"), new FloatFeature("$pp_feature_theelder_6", "Feature 6 - Pickaxes skill", "Effective Pickaxes skill levels added.", 12f, 0f, 100f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddSkillLevel((SkillType)12, v); }, "SkillLevel: { Pickaxes: {v} }"), new FloatFeature("$pp_feature_theelder_7", "Feature 7 - Regeneration tick", "Health healed every 10 seconds.", 1f, 0f, 50f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddHealthTick(v, 10f, 0f); }, "HealthTick: { Health: {v}, Interval: 10 }") } }, new BossPower { PowerName = "GP_Bonemass", PassiveName = "PP_Bonemass", BossKey = "defeated_bonemass", YamlName = "Bonemass", LocKey = "$pp_power_bonemass", ConfigCategory = "Power 3 - Bonemass", Features = new PowerFeature[7] { new DamageModFeature("$pp_feature_bonemass_1", "Feature 1 - Blunt resistance", "Resistance to blunt damage.", (DamageType)1, (DamageModifier)7), new DamageModFeature("$pp_feature_bonemass_2", "Feature 2 - Slash resistance", "Resistance to slash damage.", (DamageType)2, (DamageModifier)7), new DamageModFeature("$pp_feature_bonemass_3", "Feature 3 - Pierce resistance", "Resistance to pierce damage.", (DamageType)4, (DamageModifier)7), new FloatFeature("$pp_feature_bonemass_4", "Feature 4 - Block stamina cost", "Fraction by which blocking stamina cost changes. Negative is cheaper.", -0.5f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_blockStaminaUseModifier = v; }, "BlockStaminaCost: {v}"), new DamageModFeature("$pp_feature_bonemass_5", "Feature 5 - Poison resistance", "Resistance to poison damage.", (DamageType)256, (DamageModifier)1), new FloatFeature("$pp_feature_bonemass_6", "Feature 6 - Armor", "Flat armor added.", 6f, 0f, 200f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { SE_Stats se = b.Se; se.m_addArmor += v; }, "Armor: {v}"), new FloatFeature("$pp_feature_bonemass_7", "Feature 7 - Stagger taken", "Fraction by which stagger buildup changes. Negative staggers less.", -0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_staggerModifier = v; }, "StaggerTaken: {v}") } }, new BossPower { PowerName = "GP_Moder", PassiveName = "PP_Moder", BossKey = "defeated_dragon", YamlName = "Moder", LocKey = "$pp_power_moder", ConfigCategory = "Power 4 - Moder", Features = new PowerFeature[7] { new DamageModFeature("$pp_feature_moder_1", "Feature 1 - Frost resistance", "Resistance to frost damage.", (DamageType)64, (DamageModifier)1), new AttributeFeature("$pp_feature_moder_2", "Feature 2 - Cold immunity", "Grants the cold resistance attribute, so freezing weather no longer harms you.", (StatusAttribute)1), new AttributeFeature("$pp_feature_moder_3", "Feature 3 - Sailing tailwind", "Grants the sailing power attribute, so ships you crew always have the wind behind them.", (StatusAttribute)4), new FloatFeature("$pp_feature_moder_4", "Feature 4 - Carry weight", "Extra carry weight in units.", 150f, 0f, 1000f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { SE_Stats se = b.Se; se.m_addMaxCarryWeight += v; }, "CarryWeight: {v}"), new FloatFeature("$pp_feature_moder_5", "Feature 5 - Movement speed", "Fraction added to movement speed.", 0.05f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_speedModifier = v; }, "MovementSpeed: {v}"), new FloatFeature("$pp_feature_moder_6", "Feature 6 - Wind at your back", "Fraction by which running with the wind speeds you up and drains less stamina.", 0.25f, 0f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_windMovementModifier = v; b.Se.m_windRunStaminaModifier = 0f - v; }, "WindMovement: {v}\nWindRunStaminaCost: {-v}"), new FloatFeature("$pp_feature_moder_7", "Feature 7 - Swim speed", "Fraction added to swim speed.", 0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_swimSpeedModifier = v; }, "SwimSpeed: {v}") } }, new BossPower { PowerName = "GP_Yagluth", PassiveName = "PP_Yagluth", BossKey = "defeated_goblinking", YamlName = "Yagluth", LocKey = "$pp_power_yagluth", ConfigCategory = "Power 5 - Yagluth", Features = new PowerFeature[7] { new DamageModFeature("$pp_feature_yagluth_1", "Feature 1 - Lightning resistance", "Resistance to lightning damage.", (DamageType)128, (DamageModifier)1), new FloatFeature("$pp_feature_yagluth_2", "Feature 2 - Melee damage", "Fraction added to blunt, slash and pierce damage you deal.", 0.05f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_percentigeDamageModifiers.m_blunt += v; b.Se.m_percentigeDamageModifiers.m_slash += v; b.Se.m_percentigeDamageModifiers.m_pierce += v; }, "DamageDealt: { Melee: {v} }"), new FloatFeature("$pp_feature_yagluth_3", "Feature 3 - Farming skill", "Effective Farming skill levels added.", 12f, 0f, 100f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddSkillLevel((SkillType)106, v); }, "SkillLevel: { Farming: {v} }"), new FloatFeature("$pp_feature_yagluth_4", "Feature 4 - Elemental damage", "Fraction added to fire, frost, lightning, poison and spirit damage you deal.", 0.05f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_percentigeDamageModifiers.m_fire += v; b.Se.m_percentigeDamageModifiers.m_frost += v; b.Se.m_percentigeDamageModifiers.m_lightning += v; b.Se.m_percentigeDamageModifiers.m_poison += v; b.Se.m_percentigeDamageModifiers.m_spirit += v; }, "DamageDealt: { Elemental: {v} }"), new FloatFeature("$pp_feature_yagluth_5", "Feature 5 - Skill gain rate", "Fraction added to how fast every skill is raised.", 0.15f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) b.Se.m_raiseSkill = (SkillType)999; b.Se.m_raiseSkillModifier = v; }, "SkillGain: { All: {v} }"), new FloatFeature("$pp_feature_yagluth_6", "Feature 6 - Armor multiplier", "Fraction added to total armor.", 0.1f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_armorMultiplier = v; }, "ArmorMultiplier: {v}"), new FloatFeature("$pp_feature_yagluth_7", "Feature 7 - Mastery, all damage", "Fraction added to every damage type you deal, on top of features 2 and 4.", 0.05f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_percentigeDamageModifiers.m_damage += v; b.Se.m_percentigeDamageModifiers.m_blunt += v; b.Se.m_percentigeDamageModifiers.m_slash += v; b.Se.m_percentigeDamageModifiers.m_pierce += v; b.Se.m_percentigeDamageModifiers.m_chop += v; b.Se.m_percentigeDamageModifiers.m_pickaxe += v; b.Se.m_percentigeDamageModifiers.m_fire += v; b.Se.m_percentigeDamageModifiers.m_frost += v; b.Se.m_percentigeDamageModifiers.m_lightning += v; b.Se.m_percentigeDamageModifiers.m_poison += v; b.Se.m_percentigeDamageModifiers.m_spirit += v; }, "DamageDealt: { All: {v} }") } }, new BossPower { PowerName = "GP_Queen", PassiveName = "PP_Queen", BossKey = "defeated_queen", YamlName = "Queen", LocKey = "$pp_power_queen", ConfigCategory = "Power 6 - The Queen", Features = new PowerFeature[7] { new DamageModFeature("$pp_feature_queen_1", "Feature 1 - Poison resistance", "Resistance to poison damage.", (DamageType)256, (DamageModifier)1), new FloatFeature("$pp_feature_queen_2", "Feature 2 - Sneak stamina cost", "Fraction by which sneaking stamina cost changes. Negative is cheaper.", -0.5f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_sneakStaminaUseModifier = v; }, "SneakStaminaCost: {v}"), new FloatFeature("$pp_feature_queen_3", "Feature 3 - Eitr regeneration", "Fraction added to eitr regeneration.", 0.5f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_eitrRegenMultiplier = 1f + v; }, "EitrRegen: {v}"), new FloatFeature("$pp_feature_queen_4", "Feature 4 - Stealth", "Fraction added to how hidden you are while sneaking.", 0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_stealthModifier = v; }, "Stealth: {v}"), new FloatFeature("$pp_feature_queen_5", "Feature 5 - Noise", "Fraction by which the noise you make changes. Negative is quieter.", -0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_noiseModifier = v; }, "Noise: {v}"), new FloatFeature("$pp_feature_queen_6", "Feature 6 - Elemental Magic skill", "Effective Elemental Magic skill levels added.", 12f, 0f, 100f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddSkillLevel((SkillType)9, v); }, "SkillLevel: { ElementalMagic: {v} }"), new FloatFeature("$pp_feature_queen_7", "Feature 7 - Blood Magic skill", "Effective Blood Magic skill levels added.", 12f, 0f, 100f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { b.AddSkillLevel((SkillType)10, v); }, "SkillLevel: { BloodMagic: {v} }") } }, new BossPower { PowerName = "GP_Fader", PassiveName = "PP_Fader", BossKey = "defeated_fader", YamlName = "Fader", LocKey = "$pp_power_fader", ConfigCategory = "Power 7 - Fader", Features = new PowerFeature[7] { new DamageModFeature("$pp_feature_fader_1", "Feature 1 - Fire resistance", "Resistance to fire damage.", (DamageType)32, (DamageModifier)1), new FloatFeature("$pp_feature_fader_2", "Feature 2 - Adrenaline gain", "Fraction added to adrenaline gained in combat.", 0.5f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_adrenalineModifier = v; }, "AdrenalineGain: {v}"), new FloatFeature("$pp_feature_fader_3", "Feature 3 - Stagger taken", "Fraction by which stagger buildup changes. Negative staggers less.", -0.25f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_staggerModifier = v; }, "StaggerTaken: {v}"), new FloatFeature("$pp_feature_fader_4", "Feature 4 - Attack stamina cost", "Fraction by which attack stamina cost changes. Negative is cheaper.", -0.2f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_attackStaminaUseModifier = v; }, "AttackStaminaCost: {v}"), new FloatFeature("$pp_feature_fader_5", "Feature 5 - Armor", "Flat armor added.", 8f, 0f, 200f, ValueDisplay.Flat, delegate(PassiveBuild b, float v) { SE_Stats se = b.Se; se.m_addArmor += v; }, "Armor: {v}"), new FloatFeature("$pp_feature_fader_6", "Feature 6 - Timed block bonus", "Fraction added to the parry bonus of a timed block.", 0.5f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_timedBlockBonus = v; }, "TimedBlockBonus: {v}"), new FloatFeature("$pp_feature_fader_7", "Feature 7 - Health regeneration", "Fraction added to health regeneration.", 0.2f, -1f, 5f, ValueDisplay.Percent, delegate(PassiveBuild b, float v) { b.Se.m_healthRegenMultiplier = 1f + v; }, "HealthRegen: {v}") } } }; } } [BepInPlugin("MidnightsFX.ProgressivePowers", "ProgressivePowers", "0.3.7")] [BepInDependency(/*Could not decode attribute arguments.*/)] [SynchronizationMode(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal class ProgressivePowers : BaseUnityPlugin { public const string PluginGUID = "MidnightsFX.ProgressivePowers"; public const string PluginName = "ProgressivePowers"; public const string PluginVersion = "0.3.7"; internal static ManualLogSource Log; internal ValConfig cfg; public static CustomLocalization Localization; private const float RefreshInterval = 2f; private Harmony harmony; public void Awake() { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown Localization = LocalizationManager.Instance.GetLocalization(); Log = ((BaseUnityPlugin)this).Logger; cfg = new ValConfig(((BaseUnityPlugin)this).Config); LocalizationLoader.AddLocalizations(); AdvancedPowers.Initialize(); LevelRequirements.Initialize(); PrefabManager.OnVanillaPrefabsAvailable += PassivePowerManager.BuildPassiveEffects; SynchronizationManager.OnConfigurationSynchronized += delegate { ValConfig.MarkChanged(); PassivePowerManager.Refresh(force: true); }; CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new KillCommands.GiveCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new KillCommands.ResetCommand()); harmony = new Harmony("MidnightsFX.ProgressivePowers"); harmony.PatchAll(typeof(ProgressivePowers).Assembly); ((MonoBehaviour)this).InvokeRepeating("SafetyNetTick", 2f, 2f); ValConfig.SaveOnSet(enabled: true); ValConfig.WatchConfigFile(); } private void SafetyNetTick() { PassivePowerManager.Refresh(); } public void OnDestroy() { ((MonoBehaviour)this).CancelInvoke("SafetyNetTick"); PrefabManager.OnVanillaPrefabsAvailable -= PassivePowerManager.BuildPassiveEffects; AdvancedPowers.Shutdown(); LevelRequirements.Shutdown(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } } namespace ProgressivePowers.Patches { [HarmonyPatch(typeof(Player), "StartGuardianPower")] internal static class BlockStartGuardianPower { private static bool Prefix(Player __instance, ref bool __result) { if (!ValConfig.BlockPowerActivation.Value) { return true; } __result = false; if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { ((Character)__instance).Message((MessageType)2, "$pp_cannot_activate", 0, (Sprite)null, false); } return false; } } [HarmonyPatch(typeof(Player), "ActivateGuardianPower")] internal static class BlockActivateGuardianPower { private static bool Prefix(ref bool __result) { if (!ValConfig.BlockPowerActivation.Value) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Player), "ActivateGuardianPower")] internal static class ActivateEveryAttunement { private const float ShareRange = 10f; private static void Prefix(Player __instance, out bool __state) { StatusEffect val = default(StatusEffect); float num = default(float); __instance.GetGuardianPowerHUD(ref val, ref num); __state = (Object)(object)val != (Object)null && num <= 0f; } private static void Postfix(Player __instance, bool __state) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (!__state || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || ValConfig.BlockPowerActivation.Value) { return; } List<BossPower> list = Attunements.Active(__instance); string guardianPowerName = __instance.GetGuardianPowerName(); if (list.Count < 2 || list[list.Count - 1].PowerName != guardianPowerName) { return; } List<Player> list2 = new List<Player>(); Player.GetPlayersInRange(((Component)__instance).transform.position, 10f, list2); float num = __instance.m_guardianPowerCooldown; foreach (BossPower item in list) { if (item.PowerName == guardianPowerName) { continue; } StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode(item.PowerName)); if ((Object)(object)statusEffect == (Object)null) { continue; } foreach (Player item2 in list2) { ((Character)item2).GetSEMan().AddStatusEffect(statusEffect.NameHash(), true, 0, 0f, (short)(-1)); } num = Mathf.Max(num, statusEffect.m_cooldown); } __instance.m_guardianPowerCooldown = num; } } internal static class KillPersistence { [HarmonyPatch(typeof(Player), "Load")] internal static class LoadKills { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { KillTracker.Bind(__instance); } } } [HarmonyPatch(typeof(Player), "Save")] internal static class SaveKills { private static void Prefix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { KillTracker.Flush(__instance); } } } } [HarmonyPatch(typeof(Player), "SetGuardianPower")] internal static class OnSetGuardianPower { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { ClearStaleCooldown(__instance); PassivePowerManager.Refresh(force: true); } } internal static void ClearStaleCooldown(Player player) { if (ValConfig.BlockPowerActivation.Value) { player.m_guardianPowerCooldown = 0f; } } } [HarmonyPatch(typeof(ItemStand), "Interact")] internal static class ReleaseAttunement { private static bool Prefix(ItemStand __instance, Humanoid user, bool hold, ref bool __result) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (Attunements.Max <= 1) { return true; } if ((Object)(object)__instance.m_guardianPower == (Object)null || __instance.m_canBeRemoved || !__instance.HaveAttachment()) { return true; } Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } if (!PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { return true; } if (hold) { __result = false; return false; } BossPower bossPower = PowerDefinitions.ByPowerName(((Object)__instance.m_guardianPower).name); if (bossPower == null || ((MonoBehaviour)__instance).IsInvoking("DelayedPowerActivation") || !Attunements.IsAttuned(val, bossPower)) { return true; } Attunements.Release(val, bossPower); Localization instance = Localization.instance; ((Character)val).Message((MessageType)2, instance.Localize("$pp_released").Replace("$1", instance.Localize(bossPower.LocKey)), 0, (Sprite)null, false); __result = true; return false; } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class OnPlayerSpawned { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { OnSetGuardianPower.ClearStaleCooldown(__instance); KillTracker.EnsureLoaded(__instance); PassivePowerManager.Refresh(force: true); } } } [HarmonyPatch(typeof(Player), "OnRespawn")] internal static class OnPlayerRespawn { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { PassivePowerManager.Refresh(force: true); } } } internal static class KillBroadcast { [HarmonyPatch(typeof(Game), "Start")] internal static class RegisterKillRpc { private static void Postfix() { ZRoutedRpc.instance.Register<string, Vector3>("MidnightsFX.ProgressivePowers.BossKill", (Action<long, string, Vector3>)OnBossKill); } } internal struct KillSite { internal bool Owned; internal Vector3 Position; } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class CountBossKills { private static void Prefix(Character __instance, out KillSite __state) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) __state = default(KillSite); if (!string.IsNullOrEmpty(__instance.m_defeatSetGlobalKey) && !((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsOwner()) { __state.Owned = true; __state.Position = ((Component)__instance).transform.position; } } private static void Postfix(Character __instance, KillSite __state) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (__state.Owned) { BossPower bossPower = PowerDefinitions.ByBossKey(__instance.m_defeatSetGlobalKey); if (bossPower != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "MidnightsFX.ProgressivePowers.BossKill", new object[2] { bossPower.YamlName, __state.Position }); } } } } private const string RpcName = "MidnightsFX.ProgressivePowers.BossKill"; private static void OnBossKill(long sender, string yamlName, Vector3 pos) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) BossPower bossPower = PowerDefinitions.ByYamlName(yamlName); if (bossPower == null) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { float value = ValConfig.KillCreditRange.Value; if (value > 0f && Vector3.Distance(((Component)localPlayer).transform.position, pos) > value) { Logger.LogDebug($"{bossPower.YamlName} died outside the {value}m credit range; no kill recorded."); return; } KillTracker.RecordKill(localPlayer, bossPower); PassivePowerManager.Refresh(force: true); } } } [HarmonyPatch(typeof(SE_Stats), "GetTooltipString")] internal static class PowerTooltipText { private static void Postfix(SE_Stats __instance, ref string __result) { BossPower bossPower = PowerTooltips.Match(((Object)__instance).name); if (bossPower != null) { __result = PowerTooltips.Build(bossPower); } } } [HarmonyPatch(typeof(TextsDialog), "AddActiveEffects")] internal static class CompendiumActiveEffects { private static bool Prefix(TextsDialog __instance) { //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown Player localPlayer = Player.m_localPlayer; IReadOnlyList<BossPower> attuned = PassivePowerManager.Attuned; if ((Object)(object)localPlayer == (Object)null || attuned.Count == 0) { return true; } Localization instance = Localization.instance; List<StatusEffect> list = new List<StatusEffect>(); ((Character)localPlayer).GetSEMan().GetHUDStatusEffects(list); StringBuilder stringBuilder = new StringBuilder(256); foreach (StatusEffect item in list) { if (!IsPassive(item)) { stringBuilder.Append("<color=orange>").Append(instance.Localize(item.m_name)).Append("</color>\n"); stringBuilder.Append(instance.Localize(item.GetTooltipString())); stringBuilder.Append("\n\n"); } } StatusEffect val = default(StatusEffect); float num = default(float); localPlayer.GetGuardianPowerHUD(ref val, ref num); if ((Object)(object)val != (Object)null && PowerDefinitions.ByPowerName(((Object)val).name) == null) { stringBuilder.Append("<color=yellow>").Append(instance.Localize("$inventory_selectedgp")).Append("</color>\n"); stringBuilder.Append("<color=orange>").Append(instance.Localize(val.m_name)).Append("</color>\n"); stringBuilder.Append(instance.Localize(val.GetTooltipString())); stringBuilder.Append("\n\n"); } stringBuilder.Append(PowerTooltips.BuildCombined(attuned, PassivePowerManager.Levels, PassivePowerManager.MaxLevels)); __instance.m_texts.Insert(0, new TextInfo(instance.Localize("$inventory_activeeffects"), stringBuilder.ToString())); return false; } private static bool IsPassive(StatusEffect se) { BossPower[] all = PowerDefinitions.All; foreach (BossPower bossPower in all) { if (((Object)se).name == bossPower.PassiveName) { return true; } } return false; } } [HarmonyPatch(typeof(ItemStand), "GetHoverText")] internal static class BossStoneHoverText { private const string UseKey = "[<color=yellow><b>$KEY_Use</b></color>] "; private static void Postfix(ItemStand __instance, ref string __result) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance.m_guardianPower == (Object)null) && !__instance.m_canBeRemoved && !((Object)(object)Player.m_localPlayer == (Object)null) && !string.IsNullOrEmpty(__result) && __instance.HaveAttachment() && PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { BossPower bossPower = PowerDefinitions.ByPowerName(((Object)__instance.m_guardianPower).name); if (bossPower != null) { string text = Localization.instance.Localize("<color=orange>" + __instance.m_guardianPower.m_name + "</color>"); __result = text + "\n" + PowerTooltips.Build(bossPower) + "\n\n" + ActionText(Player.m_localPlayer, bossPower); } } } private static string ActionText(Player player, BossPower power) { Localization instance = Localization.instance; bool flag = Attunements.IsAttuned(player, power); if (Attunements.Max <= 1) { return instance.Localize(flag ? "$pp_attuned" : "[<color=yellow><b>$KEY_Use</b></color>] $pp_attune"); } if (flag) { return instance.Localize("$pp_attuned\n[<color=yellow><b>$KEY_Use</b></color>] $pp_release"); } BossPower bossPower = Attunements.Displaced(player); if (bossPower == null) { return instance.Localize("[<color=yellow><b>$KEY_Use</b></color>] $pp_attune"); } return instance.Localize("[<color=yellow><b>$KEY_Use</b></color>] $pp_attune_replacing").Replace("$1", instance.Localize(bossPower.LocKey)); } } [HarmonyPatch(typeof(Hud), "UpdateGuardianPower")] internal static class GuardianPowerHudLevel { private static void Postfix(Hud __instance, Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { PowerHud.Update(__instance, player); } } } } namespace ProgressivePowers.Modules { internal static class EffectCatalog { internal abstract class Kind { internal string Key; internal string Doc; internal string Shape; internal abstract void Parse(object value, string where, List<PassiveEffect> into); } private sealed class NumberKind : Kind { internal string LocKey; internal ValueDisplay Display; internal Action<PassiveBuild, float> Apply; internal bool AddsUp; internal override void Parse(object value, string where, List<PassiveEffect> into) { if (!TryFloat(value, out var result)) { Logger.LogWarning(where + ": " + Key + " needs a number, got " + Show(value) + ". Ignored."); } else { into.Add(new NumberEffect(this, result)); } } } private sealed class NumberEffect : PassiveEffect { private readonly NumberKind kind; private readonly float value; internal override bool Enabled => Math.Abs(value) > 0.0001f; internal override string Template { get { if (!kind.AddsUp) { return null; } return PassiveEffect.Localize(kind.LocKey); } } internal override float Amount => value; internal override ValueDisplay Display => kind.Display; internal NumberEffect(NumberKind kind, float value) { this.kind = kind; this.value = value; } inter