using 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.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using PotionMaster.Patches; using PotionMaster.Sync; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PotionMaster")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+4aa3e4b9cec60eb3ac3a77a521c68480a0a8472a")] [assembly: AssemblyProduct("PotionMaster")] [assembly: AssemblyTitle("PotionMaster")] [assembly: AssemblyVersion("1.0.0.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 PotionMaster { [BepInPlugin("com.emergent.potionmaster", "PotionMaster", "1.0.0")] [BepInProcess("valheim.exe")] [BepInProcess("valheim_server.exe")] public class PotionMasterPlugin : BaseUnityPlugin { public const string PluginGuid = "com.emergent.potionmaster"; public const string PluginName = "PotionMaster"; public const string PluginVersion = "1.0.0"; private Harmony _harmony; private float _lastConsumeTime; public static PotionMasterPlugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static PotionMasterConfig Cfg { get; private set; } private void Awake() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; string text = Path.Combine(Paths.ConfigPath, "PotionMaster.cfg"); Cfg = new PotionMasterConfig(new ConfigFile(text, true)); _harmony = new Harmony("com.emergent.potionmaster"); _harmony.PatchAll(typeof(PlayerPatch)); ConfigSync.Init(this); Log.LogInfo((object)("PotionMaster v1.0.0 loaded. Config: " + text)); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if (!Cfg.ModEnabled.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || ((Character)localPlayer).InCutscene() || Console.IsVisible() || ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) || TextInput.IsVisible() || InventoryGui.IsVisible() || Menu.IsVisible() || StoreGui.IsVisible() || ((Object)(object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible())) { return; } if (Input.GetKeyDown(Cfg.HealthKey.Value)) { TryConsume(localPlayer, PotionType.Health, manual: true); } if (Input.GetKeyDown(Cfg.StaminaKey.Value)) { TryConsume(localPlayer, PotionType.Stamina, manual: true); } if (Input.GetKeyDown(Cfg.EitrKey.Value)) { TryConsume(localPlayer, PotionType.Eitr, manual: true); } if (Cfg.AutoPotionEnabled.Value) { float maxHealth = ((Character)localPlayer).GetMaxHealth(); float health = ((Character)localPlayer).GetHealth(); if (maxHealth > 0f && health / maxHealth * 100f <= Cfg.AutoHealthThreshold.Value) { TryConsume(localPlayer, PotionType.Health, manual: false); } float maxStamina = ((Character)localPlayer).GetMaxStamina(); float stamina = localPlayer.GetStamina(); if (maxStamina > 0f && stamina / maxStamina * 100f <= Cfg.AutoStaminaThreshold.Value) { TryConsume(localPlayer, PotionType.Stamina, manual: false); } float maxEitr = ((Character)localPlayer).GetMaxEitr(); float eitr = localPlayer.GetEitr(); if (maxEitr > 0f && eitr / maxEitr * 100f <= Cfg.AutoEitrThreshold.Value) { TryConsume(localPlayer, PotionType.Eitr, manual: false); } } } private void TryConsume(Player player, PotionType type, bool manual) { if (!(Time.time - _lastConsumeTime < Cfg.ConsumeInterval.Value) && PotionConsumer.Consume(player, type, out var itemName)) { _lastConsumeTime = Time.time; if (Cfg.ShowMessages.Value && manual) { ((Character)player).Message((MessageType)1, "[PotionMaster] " + itemName, 0, (Sprite)null); } } } } public static class PotionConsumer { public static bool Consume(Player player, PotionType type, out string itemName) { itemName = null; if ((Object)(object)player == (Object)null) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return false; } List priorityList = GetPriorityList(type); if (priorityList.Count == 0) { return false; } foreach (string item in priorityList) { ItemData val = FindItemByPrefabName(inventory, item); if (val != null && CanConsume(player, val) && DoConsume(player, val)) { itemName = val.m_shared.m_name; return true; } } return false; } private static List GetPriorityList(PotionType type) { object obj = type switch { PotionType.Health => PotionMasterPlugin.Cfg.HealthPotionPriority.Value, PotionType.Stamina => PotionMasterPlugin.Cfg.StaminaPotionPriority.Value, PotionType.Eitr => PotionMasterPlugin.Cfg.EitrPotionPriority.Value, _ => "", }; List list = new List(); string[] array = ((string)obj).Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } return list; } private static ItemData FindItemByPrefabName(Inventory inv, string prefabName) { foreach (ItemData allItem in inv.GetAllItems()) { if ((Object)(object)allItem?.m_dropPrefab != (Object)null && ((Object)allItem.m_dropPrefab).name.Equals(prefabName, StringComparison.OrdinalIgnoreCase)) { return allItem; } } return null; } private static bool CanConsume(Player player, ItemData item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 if ((int)item.m_shared.m_itemType != 2) { return false; } StatusEffect consumeStatusEffect = item.m_shared.m_consumeStatusEffect; if ((Object)(object)consumeStatusEffect != (Object)null && ((Character)player).GetSEMan().HaveStatusEffect(consumeStatusEffect.NameHash())) { return false; } return player.CanEat(item, false); } private static bool DoConsume(Player player, ItemData item) { Inventory inventory = ((Humanoid)player).GetInventory(); return ((Humanoid)player).ConsumeItem(inventory, item, false); } } public enum PotionType { Health, Stamina, Eitr } public class PotionMasterConfig { public readonly ConfigEntry ModEnabled; public readonly ConfigEntry HealthKey; public readonly ConfigEntry StaminaKey; public readonly ConfigEntry EitrKey; public readonly ConfigEntry AutoPotionEnabled; public readonly ConfigEntry AutoHealthThreshold; public readonly ConfigEntry AutoStaminaThreshold; public readonly ConfigEntry AutoEitrThreshold; public readonly ConfigEntry ConsumeInterval; public readonly ConfigEntry BypassCooldownAnimation; public readonly ConfigEntry ShowMessages; public readonly ConfigEntry HealthPotionPriority; public readonly ConfigEntry StaminaPotionPriority; public readonly ConfigEntry EitrPotionPriority; public readonly ConfigEntry ServerSyncEnabled; public readonly ConfigEntry ServerForceConfig; public PotionMasterConfig(ConfigFile cfg) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown ModEnabled = cfg.Bind("1 - General", "ModEnabled", true, "Master switch for PotionMaster. Set to false to fully disable."); HealthKey = cfg.Bind("2 - Hotkeys", "HealthKey", (KeyCode)49, "Key to quick-consume a Health mead."); StaminaKey = cfg.Bind("2 - Hotkeys", "StaminaKey", (KeyCode)50, "Key to quick-consume a Stamina mead."); EitrKey = cfg.Bind("2 - Hotkeys", "EitrKey", (KeyCode)51, "Key to quick-consume an Eitr mead."); AutoPotionEnabled = cfg.Bind("3 - AutoPotion", "AutoPotionEnabled", false, "If true, PotionMaster will auto-drink meads when thresholds are crossed."); AutoHealthThreshold = cfg.Bind("3 - AutoPotion", "AutoHealthThresholdPercent", 50f, new ConfigDescription("Auto drink health mead when HP % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 99f), Array.Empty())); AutoStaminaThreshold = cfg.Bind("3 - AutoPotion", "AutoStaminaThresholdPercent", 30f, new ConfigDescription("Auto drink stamina mead when stamina % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 99f), Array.Empty())); AutoEitrThreshold = cfg.Bind("3 - AutoPotion", "AutoEitrThresholdPercent", 30f, new ConfigDescription("Auto drink eitr mead when eitr % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 99f), Array.Empty())); ConsumeInterval = cfg.Bind("4 - Consumption", "ConsumeIntervalSeconds", 1f, new ConfigDescription("Minimum seconds between any two mead consumptions.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f), Array.Empty())); BypassCooldownAnimation = cfg.Bind("4 - Consumption", "BypassDrinkAnimation", true, "Skip the slow drink animation for instant consumption. Vanilla cooldowns on mead effects are respected."); ShowMessages = cfg.Bind("4 - Consumption", "ShowConsumeMessages", true, "Show a top-left message when a mead is consumed via hotkey."); HealthPotionPriority = cfg.Bind("5 - Priority", "HealthPotionPriority", "MeadHealthMajor,MeadHealthMedium,MeadHealthMinor", "Comma-separated item prefab names in priority order. First match found in inventory is consumed."); StaminaPotionPriority = cfg.Bind("5 - Priority", "StaminaPotionPriority", "MeadStaminaLingering,MeadStaminaMedium,MeadStaminaMinor", "Comma-separated item prefab names in priority order."); EitrPotionPriority = cfg.Bind("5 - Priority", "EitrPotionPriority", "MeadEitrLingering,MeadEitrMinor", "Comma-separated item prefab names in priority order."); ServerSyncEnabled = cfg.Bind("6 - Server", "ServerSyncEnabled", true, "Sync config from server to all clients (dedicated servers)."); ServerForceConfig = cfg.Bind("6 - Server", "ServerForceConfig", false, "If true, the server overwrites client values. If false, values are merged (server takes precedence only for locked keys)."); } } } namespace PotionMaster.Sync { public static class ConfigSync { private const string RpcName = "PotionMaster_ConfigSync"; private static PotionMasterPlugin _plugin; private static readonly HashSet SyncedKeys = new HashSet { "ModEnabled", "AutoPotionEnabled", "AutoHealthThresholdPercent", "AutoStaminaThresholdPercent", "AutoEitrThresholdPercent", "ConsumeIntervalSeconds", "BypassDrinkAnimation", "HealthPotionPriority", "StaminaPotionPriority", "EitrPotionPriority", "ServerForceConfig" }; public static void Init(PotionMasterPlugin plugin) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _plugin = plugin; new Harmony("com.emergent.potionmaster.sync").PatchAll(typeof(ConfigSync)); } [HarmonyPatch(typeof(Game), "Start")] [HarmonyPostfix] private static void RegisterRpc() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("PotionMaster_ConfigSync", (Action)OnConfigReceived); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPostfix] private static void OnPeerConnected(ZNet __instance, ZRpc rpc) { if (!__instance.IsServer() || !PotionMasterPlugin.Cfg.ServerSyncEnabled.Value) { return; } ZNetPeer peer = __instance.GetPeer(rpc); if (peer == null) { return; } try { ZPackage val = BuildPackage(); ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "PotionMaster_ConfigSync", new object[1] { val }); PotionMasterPlugin.Log.LogInfo((object)$"Sent config sync to peer {peer.m_uid}"); } catch (Exception ex) { PotionMasterPlugin.Log.LogError((object)("ConfigSync send failed: " + ex.Message)); } } private static ZPackage BuildPackage() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); List<(string, string)> list = new List<(string, string)>(); foreach (KeyValuePair item in ((BaseUnityPlugin)_plugin).Config) { if (SyncedKeys.Contains(item.Key.Key)) { ConfigEntryBase entry = ((BaseUnityPlugin)_plugin).Config[item.Key]; list.Add((item.Key.Key, ValueToString(entry))); } } val.Write(list.Count); foreach (var (text, text2) in list) { val.Write(text); val.Write(text2); } return val; } private static void OnConfigReceived(long sender, ZPackage pkg) { if (!PotionMasterPlugin.Cfg.ServerSyncEnabled.Value || ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())) { return; } try { int num = pkg.ReadInt(); for (int i = 0; i < num; i++) { string key = pkg.ReadString(); string value = pkg.ReadString(); if (TryFindEntry(key, out var entry)) { SetEntryFromString(entry, value); } } PotionMasterPlugin.Log.LogInfo((object)$"Applied server config sync ({num} keys)"); } catch (Exception ex) { PotionMasterPlugin.Log.LogError((object)("ConfigSync receive failed: " + ex.Message)); } } private static bool TryFindEntry(string key, out ConfigEntryBase entry) { foreach (KeyValuePair item in ((BaseUnityPlugin)_plugin).Config) { if (item.Key.Key == key) { entry = ((BaseUnityPlugin)_plugin).Config[item.Key]; return true; } } entry = null; return false; } private static string ValueToString(ConfigEntryBase entry) { return entry.BoxedValue?.ToString() ?? ""; } private static void SetEntryFromString(ConfigEntryBase entry, string value) { try { Type settingType = entry.SettingType; object boxedValue = ((settingType == typeof(bool)) ? ((object)bool.Parse(value)) : ((settingType == typeof(float)) ? ((object)float.Parse(value, CultureInfo.InvariantCulture)) : ((settingType == typeof(int)) ? ((object)int.Parse(value, CultureInfo.InvariantCulture)) : ((!(settingType == typeof(string))) ? TomlTypeConverter.ConvertToValue(value, settingType) : value)))); entry.BoxedValue = boxedValue; } catch (Exception ex) { PotionMasterPlugin.Log.LogWarning((object)("Failed to set " + entry.Definition.Key + "=" + value + ": " + ex.Message)); } } } } namespace PotionMaster.Patches { [HarmonyPatch] public static class PlayerPatch { } }