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 Surge v1.0.3
plugins/Surge/Surge.dll
Decompiled 15 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Ezomic.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("Thijssen Software")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")] [assembly: AssemblyDescription("Configure the max adrenaline granted by trinkets.")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3+f5d32060b61cdd188ab11b66470a373af5bbfa79")] [assembly: AssemblyProduct("Surge")] [assembly: AssemblyTitle("Surge")] [assembly: AssemblyVersion("1.0.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Surge { internal static class AdrenalineTuner { private static readonly Dictionary<string, float> Originals = new Dictionary<string, float>(); private static int ApplyToInventory() { Player localPlayer = Player.m_localPlayer; Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null) { return 0; } int num = 0; foreach (ItemData allItem in val.GetAllItems()) { if (allItem != null && allItem.m_shared != null && !((Object)(object)allItem.m_dropPrefab == (Object)null) && Originals.TryGetValue(((Object)allItem.m_dropPrefab).name, out var value)) { float num2 = Target(((Object)allItem.m_dropPrefab).name, value); if (!Mathf.Approximately(allItem.m_shared.m_maxAdrenaline, num2)) { allItem.m_shared.m_maxAdrenaline = num2; num++; } } } return num; } public static void Apply(bool announce = true) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0) { return; } int num = 0; int num2 = 0; foreach (GameObject item in instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); SharedData val = (((Object)(object)component != (Object)null && component.m_itemData != null) ? component.m_itemData.m_shared : null); if (val == null) { continue; } if (!Originals.TryGetValue(((Object)item).name, out var value)) { value = val.m_maxAdrenaline; Originals[((Object)item).name] = value; } if ((int)val.m_itemType != 24 && value == 0f) { continue; } num2++; float num3 = Target(((Object)item).name, value); if (Mathf.Approximately(val.m_maxAdrenaline, num3)) { if (announce && SurgeConfig.Verbose.Value) { SurgePlugin.Log.LogInfo((object)(" " + ((Object)item).name + ": " + (Mathf.Approximately(num3, value) ? (Show(value) + " (vanilla)") : (Show(value) + " -> " + Show(num3) + " (already set)")))); } continue; } val.m_maxAdrenaline = num3; num++; if (SurgeConfig.Verbose.Value || !announce) { SurgePlugin.Log.LogInfo((object)(" " + ((Object)item).name + ": " + Show(value) + " -> " + Show(num3))); } } int num4 = ApplyToInventory(); if (announce) { SurgePlugin.Log.LogInfo((object)("Found " + num2 + " adrenaline item(s); retuned " + num + ", plus " + num4 + " in the player's inventory.")); } else if (num > 0 || num4 > 0) { SurgePlugin.Log.LogInfo((object)("Swept up " + num + " prefab(s) and " + num4 + " carried item(s) the config change did not reach.")); } } private static float Target(string prefabName, float original) { if (!SurgeConfig.Enabled.Value) { return original; } if (SurgeConfig.TryGetOverride(prefabName, out var value)) { return Mathf.Max(0f, value); } if (SurgeConfig.FlatValue.Value > 0f) { return Floor(SurgeConfig.FlatValue.Value); } if (original == 0f) { return 0f; } return Floor(original * SurgeConfig.Multiplier.Value); } private static float Floor(float value) { return Mathf.Max(Mathf.Max(0f, SurgeConfig.Minimum.Value), value); } private static string Show(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } internal static class ConfigWatcher { private static FileSystemWatcher _watcher; private static volatile bool _dirty; private static string _path; private static DateTime _stamp; public static void Start(ConfigFile config) { string configFilePath = config.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); if (string.IsNullOrEmpty(directoryName) || !Directory.Exists(directoryName)) { return; } _path = configFilePath; _stamp = SafeStamp(configFilePath); try { _watcher = new FileSystemWatcher(directoryName, Path.GetFileName(configFilePath)) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite) }; _watcher.Changed += OnTouched; _watcher.Created += OnTouched; _watcher.Renamed += OnTouched; _watcher.EnableRaisingEvents = true; } catch (Exception ex) { SurgePlugin.Log.LogWarning((object)("Could not watch the config file, so edits will be noticed by polling instead: " + ex.Message)); _watcher = null; } } public static void Stop() { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); _watcher = null; } } private static void OnTouched(object sender, FileSystemEventArgs e) { _dirty = true; } public static void Poll() { if (_path != null) { DateTime dateTime = SafeStamp(_path); if (!(dateTime == DateTime.MinValue) && !(dateTime == _stamp)) { _stamp = dateTime; _dirty = true; } } } private static DateTime SafeStamp(string path) { try { return File.GetLastWriteTimeUtc(path); } catch { return DateTime.MinValue; } } public static bool TakeDirty() { if (!_dirty) { return false; } _dirty = false; return true; } } internal static class SurgeConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<float> Multiplier; public static ConfigEntry<float> FlatValue; public static ConfigEntry<string> PerTrinket; public static ConfigEntry<float> Minimum; public static ConfigEntry<float> PlayerBase; public static ConfigEntry<bool> Verbose; private static Dictionary<string, float> _overrides; public static void Bind(ConfigFile config) { Enabled = config.Bind<bool>("Surge", "Enabled", true, "Retune the max adrenaline that trinkets grant. Off leaves every trinket at its vanilla value; nothing else in the adrenaline system is touched either way."); Multiplier = config.Bind<float>("Surge", "Multiplier", 1f, "Scales every trinket's vanilla max adrenaline, which is the amount the bar must reach before the trinket fires its effect and empties. 1 = unchanged. Above 1 the trinket needs more before it pays out; below 1 it pays out sooner and more often. Both the fill rate and the decay rate are curves over how full the bar is rather than fixed amounts, so they stretch along with it and 2 really does mean about twice as long. The one thing that does not stretch is the grace period before an idle bar starts decaying, which stays the same number of seconds - so a longer bar simply gives a lull in the fight more chance to eat into it, and at high values the payoff can become hard to reach against weak enemies. Nothing else competes with this: the game also has a tiered adrenaline buff system keyed to fixed amounts, which would not move with this setting, but it is empty on the player and so the trinket's own effect is the only payoff. Verbose reports those tiers if a game version ever fills them in."); FlatValue = config.Bind<float>("Surge", "FlatValue", 0f, "Give every trinket this exact max adrenaline, ignoring Multiplier. 0 = off. Use it to flatten the tiers deliberately; note that it applies to modded trinkets too, which is usually not what you want."); PerTrinket = config.Bind<string>("Surge", "PerTrinket", "", "Per-item values, beating both Multiplier and FlatValue. Comma-separated Prefab=Number, e.g. TrinketIronHealth=80,TrinketSilverDamage=120. Prefab names are the ones in the log line this mod writes on startup - turn Verbose on once and they are all listed."); Minimum = config.Bind<float>("Surge", "Minimum", 1f, "Floor for any trinket that grants adrenaline at all. A computed value below this is raised to it. Set to 0 to allow a trinket to be tuned down to no adrenaline at all, which disables the bar while it is equipped."); PlayerBase = config.Bind<float>("Surge", "PlayerBase", -1f, "Max adrenaline the player has with no trinket equipped, which every trinket then adds to. -1 leaves the game's own value alone. Setting this above 0 gives you an adrenaline bar permanently, trinket or not."); Verbose = config.Bind<bool>("Diagnostics", "Verbose", false, "Log every trinket found and what its max adrenaline was changed to."); PerTrinket.SettingChanged += delegate { _overrides = null; }; } public static bool TryGetOverride(string prefabName, out float value) { if (_overrides == null) { Parse(); } return _overrides.TryGetValue(prefabName, out value); } private static void Parse() { _overrides = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); string[] array = (PerTrinket.Value ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf('='); if (num <= 0) { SurgePlugin.Log.LogWarning((object)("PerTrinket: ignoring '" + text + "' - expected Prefab=Number.")); continue; } string text2 = text.Substring(0, num).Trim(); if (!float.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { SurgePlugin.Log.LogWarning((object)("PerTrinket: '" + text2 + "' has no readable number - ignored.")); } else { _overrides[text2] = result; } } } } [BepInPlugin("ezomic.valheim.surge", "Surge", "1.0.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class SurgePlugin : BaseUnityPlugin { public const string PluginGuid = "ezomic.valheim.surge"; public const string PluginName = "Surge"; public const string PluginVersion = "1.0.3"; public const string PluginAuthor = "Robbin Thijssen"; private const string CoreGuid = "ezomic.valheim.core"; internal static ManualLogSource Log; private const float SettleSeconds = 0.3f; private const float PollSeconds = 1f; private float _nextPoll; private float _lastReportedMax = float.NaN; private float _lastEquippedShared = float.NaN; private Harmony _harmony; private Player _stamped; private static volatile bool _retune; private static volatile bool _restamp; private float _reloadAt; private static float _vanillaBase = float.NaN; private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SurgeConfig.Bind(((BaseUnityPlugin)this).Config); TryRegisterWithCore(); _harmony = new Harmony("ezomic.valheim.surge"); _harmony.PatchAll(typeof(ObjectDbPatches)); SurgeConfig.Enabled.SettingChanged += Queue; SurgeConfig.Multiplier.SettingChanged += Queue; SurgeConfig.FlatValue.SettingChanged += Queue; SurgeConfig.PerTrinket.SettingChanged += Queue; SurgeConfig.Minimum.SettingChanged += Queue; SurgeConfig.Verbose.SettingChanged += Queue; SurgeConfig.PlayerBase.SettingChanged += delegate { _restamp = true; }; ConfigWatcher.Start(((BaseUnityPlugin)this).Config); Log.LogInfo((object)"Surge 1.0.3 by Robbin Thijssen - ready."); } private void OnDestroy() { ConfigWatcher.Stop(); if (_harmony != null) { _harmony.UnpatchSelf(); } } private static void Queue(object sender, EventArgs e) { _retune = true; } private void ReportLiveMax(Player player) { if (SurgeConfig.Verbose.Value) { float maxAdrenaline = ((Character)player).GetMaxAdrenaline(); if (!Mathf.Approximately(maxAdrenaline, _lastReportedMax)) { _lastReportedMax = maxAdrenaline; Log.LogInfo((object)("Local player's max adrenaline is now " + maxAdrenaline.ToString("0.##", CultureInfo.InvariantCulture) + " (0 means no trinket equipped).")); } } } private void ReportEquippedShared(Player player) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 if (!SurgeConfig.Verbose.Value) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && allItem.m_equipped && allItem.m_shared != null && (int)allItem.m_shared.m_itemType == 24) { if (!Mathf.Approximately(allItem.m_shared.m_maxAdrenaline, _lastEquippedShared)) { _lastEquippedShared = allItem.m_shared.m_maxAdrenaline; Log.LogInfo((object)("Equipped trinket's own shared max is now " + _lastEquippedShared.ToString("0.##", CultureInfo.InvariantCulture) + ". If this tracks the retune but the line above does not, the item is fine and the equipment totals are stale.")); } break; } } } private void TryRegisterWithCore() { if (!Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core")) { Log.LogInfo((object)"Core not installed - running standalone, without the version gate."); } else { RegisterWithCore(); } } [MethodImpl(MethodImplOptions.NoInlining)] private void RegisterWithCore() { Suite.Register("ezomic.valheim.surge", "Surge", "1.0.3", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null); } private void Update() { if (Time.unscaledTime >= _nextPoll) { _nextPoll = Time.unscaledTime + 1f; ConfigWatcher.Poll(); AdrenalineTuner.Apply(announce: false); } if (ConfigWatcher.TakeDirty()) { _reloadAt = Time.unscaledTime + 0.3f; } if (_reloadAt > 0f && Time.unscaledTime >= _reloadAt) { _reloadAt = 0f; Reload(); } if (_retune) { _retune = false; AdrenalineTuner.Apply(); } StampBase(); } private void Reload() { try { ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { Log.LogWarning((object)("Could not reload the config: " + ex.Message)); } } private void StampBase() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } ReportLiveMax(localPlayer); ReportEquippedShared(localPlayer); if (_restamp) { _restamp = false; _stamped = null; } if ((Object)(object)localPlayer == (Object)(object)_stamped) { return; } _stamped = localPlayer; if (float.IsNaN(_vanillaBase)) { _vanillaBase = localPlayer.m_maxAdrenaline; Log.LogInfo((object)("Player base max adrenaline (before trinkets) is " + _vanillaBase.ToString("0.##", CultureInfo.InvariantCulture) + ".")); if (SurgeConfig.Verbose.Value) { LogTiers(localPlayer); } } localPlayer.m_maxAdrenaline = ((SurgeConfig.PlayerBase.Value < 0f) ? _vanillaBase : SurgeConfig.PlayerBase.Value); } private static void LogTiers(Player player) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) List<StatusEffectLevel> adrenalineEffects = player.m_adrenalineEffects; if (adrenalineEffects == null || adrenalineEffects.Count == 0) { Log.LogInfo((object)"Player has no tiered adrenaline effects."); return; } foreach (StatusEffectLevel item in adrenalineEffects) { string text = (((Object)(object)item.m_se != (Object)null) ? ((Object)item.m_se).name : "(none)"); ManualLogSource log = Log; float rate = item.m_rate; log.LogInfo((object)(" player adrenaline tier at " + rate.ToString("0.##", CultureInfo.InvariantCulture) + ": " + text)); } } } internal static class ObjectDbPatches { [HarmonyPostfix] [HarmonyPatch(typeof(ObjectDB), "Awake")] private static void TuneOnAwake() { AdrenalineTuner.Apply(); } [HarmonyPostfix] [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static void TuneOnCopy() { AdrenalineTuner.Apply(); } } }