Decompiled source of PotionMaster v1.0.0

PotionMaster.dll

Decompiled 2 weeks ago
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<string> 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<string> 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<string> list = new List<string>();
			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<bool> ModEnabled;

		public readonly ConfigEntry<KeyCode> HealthKey;

		public readonly ConfigEntry<KeyCode> StaminaKey;

		public readonly ConfigEntry<KeyCode> EitrKey;

		public readonly ConfigEntry<bool> AutoPotionEnabled;

		public readonly ConfigEntry<float> AutoHealthThreshold;

		public readonly ConfigEntry<float> AutoStaminaThreshold;

		public readonly ConfigEntry<float> AutoEitrThreshold;

		public readonly ConfigEntry<float> ConsumeInterval;

		public readonly ConfigEntry<bool> BypassCooldownAnimation;

		public readonly ConfigEntry<bool> ShowMessages;

		public readonly ConfigEntry<string> HealthPotionPriority;

		public readonly ConfigEntry<string> StaminaPotionPriority;

		public readonly ConfigEntry<string> EitrPotionPriority;

		public readonly ConfigEntry<bool> ServerSyncEnabled;

		public readonly ConfigEntry<bool> 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<bool>("1 - General", "ModEnabled", true, "Master switch for PotionMaster. Set to false to fully disable.");
			HealthKey = cfg.Bind<KeyCode>("2 - Hotkeys", "HealthKey", (KeyCode)49, "Key to quick-consume a Health mead.");
			StaminaKey = cfg.Bind<KeyCode>("2 - Hotkeys", "StaminaKey", (KeyCode)50, "Key to quick-consume a Stamina mead.");
			EitrKey = cfg.Bind<KeyCode>("2 - Hotkeys", "EitrKey", (KeyCode)51, "Key to quick-consume an Eitr mead.");
			AutoPotionEnabled = cfg.Bind<bool>("3 - AutoPotion", "AutoPotionEnabled", false, "If true, PotionMaster will auto-drink meads when thresholds are crossed.");
			AutoHealthThreshold = cfg.Bind<float>("3 - AutoPotion", "AutoHealthThresholdPercent", 50f, new ConfigDescription("Auto drink health mead when HP % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 99f), Array.Empty<object>()));
			AutoStaminaThreshold = cfg.Bind<float>("3 - AutoPotion", "AutoStaminaThresholdPercent", 30f, new ConfigDescription("Auto drink stamina mead when stamina % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 99f), Array.Empty<object>()));
			AutoEitrThreshold = cfg.Bind<float>("3 - AutoPotion", "AutoEitrThresholdPercent", 30f, new ConfigDescription("Auto drink eitr mead when eitr % is below this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 99f), Array.Empty<object>()));
			ConsumeInterval = cfg.Bind<float>("4 - Consumption", "ConsumeIntervalSeconds", 1f, new ConfigDescription("Minimum seconds between any two mead consumptions.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
			BypassCooldownAnimation = cfg.Bind<bool>("4 - Consumption", "BypassDrinkAnimation", true, "Skip the slow drink animation for instant consumption. Vanilla cooldowns on mead effects are respected.");
			ShowMessages = cfg.Bind<bool>("4 - Consumption", "ShowConsumeMessages", true, "Show a top-left message when a mead is consumed via hotkey.");
			HealthPotionPriority = cfg.Bind<string>("5 - Priority", "HealthPotionPriority", "MeadHealthMajor,MeadHealthMedium,MeadHealthMinor", "Comma-separated item prefab names in priority order. First match found in inventory is consumed.");
			StaminaPotionPriority = cfg.Bind<string>("5 - Priority", "StaminaPotionPriority", "MeadStaminaLingering,MeadStaminaMedium,MeadStaminaMinor", "Comma-separated item prefab names in priority order.");
			EitrPotionPriority = cfg.Bind<string>("5 - Priority", "EitrPotionPriority", "MeadEitrLingering,MeadEitrMinor", "Comma-separated item prefab names in priority order.");
			ServerSyncEnabled = cfg.Bind<bool>("6 - Server", "ServerSyncEnabled", true, "Sync config from server to all clients (dedicated servers).");
			ServerForceConfig = cfg.Bind<bool>("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<string> SyncedKeys = new HashSet<string>
		{
			"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<ZPackage>("PotionMaster_ConfigSync", (Action<long, ZPackage>)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<ConfigDefinition, ConfigEntryBase> 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<ConfigDefinition, ConfigEntryBase> 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
	{
	}
}