using System; using System.Collections.Generic; using System.Diagnostics; 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 Ezomic.Shared; 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("Lead a tamed boar or hen on a crafted halter.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+65d55ce3fad9f553ca8123356c93a24835772f42")] [assembly: AssemblyProduct("Taum")] [assembly: AssemblyTitle("Taum")] [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 Ezomic.Shared { public static class Prefabs { private sealed class Kept { internal string Name; internal Func Build; internal bool Item; internal string Tool; internal GameObject Prefab; internal int Failures; internal bool Abandoned; } private static ManualLogSource _log; private static GameObject _holder; private static readonly List Standing = new List(); private const int MaxFailures = 5; private static readonly HashSet Complained = new HashSet(); private static FieldRef> _named; private static MethodInfo _updateRegisters; public static ManualLogSource Log { get { return _log ?? (_log = Logger.CreateLogSource("Prefabs")); } set { _log = value; } } public static Transform Holder { get { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown if ((Object)(object)_holder == (Object)null) { _holder = new GameObject(Log.SourceName + "Prefabs"); _holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_holder); } return _holder.transform; } } public static void Keep(string name, Func build, bool item = false, string buildTool = null) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } if (build == null) { throw new ArgumentNullException("build"); } foreach (Kept item2 in Standing) { if (!(item2.Name != name)) { item2.Build = build; item2.Item = item; item2.Tool = buildTool; item2.Failures = 0; item2.Abandoned = false; return; } } Standing.Add(new Kept { Name = name, Build = build, Item = item, Tool = buildTool }); Log.LogInfo((object)("Keeping " + name + " registered.")); } public static void Drop(string name) { Standing.RemoveAll((Kept k) => k.Name == name); } public static void Tick() { if (Standing.Count == 0) { return; } ZNetScene instance = ZNetScene.instance; if (!((Object)(object)instance == (Object)null)) { for (int i = 0; i < Standing.Count; i++) { Apply(Standing[i], instance); } } } private static void Apply(Kept kept, ZNetScene scene) { if (!kept.Abandoned && (!((Object)(object)kept.Prefab == (Object)null) || TryBuild(kept))) { if ((Object)(object)scene.GetPrefab(kept.Name) == (Object)null) { Register(kept.Prefab); } if (kept.Item) { RegisterItem(kept.Prefab); } if (!string.IsNullOrEmpty(kept.Tool)) { AddToTool(kept.Prefab, kept.Tool); } } } private static bool TryBuild(Kept kept) { GameObject val = null; try { val = kept.Build(); } catch (Exception ex) { Log.LogWarning((object)("Building " + kept.Name + " failed: " + ex.Message)); } if ((Object)(object)val != (Object)null) { kept.Prefab = val; kept.Failures = 0; if (((Object)val).name != kept.Name) { ((Object)val).name = kept.Name; } return true; } if (++kept.Failures < 5) { return false; } kept.Abandoned = true; Log.LogError((object)(kept.Name + " could not be built after " + 5 + " attempts and will not be retried. Anything already built with it in a world is untouched; it simply will not be placeable this session.")); return false; } public static bool Known(string name) { if ((Object)(object)ZNetScene.instance != (Object)null) { return (Object)(object)ZNetScene.instance.GetPrefab(name) != (Object)null; } return false; } public static GameObject Clone(GameObject source, string name) { if ((Object)(object)source == (Object)null) { return null; } bool forceDisableInit = ZNetView.m_forceDisableInit; ZNetView.m_forceDisableInit = true; GameObject val; try { val = Object.Instantiate(source, Holder); } finally { ZNetView.m_forceDisableInit = forceDisableInit; } ((Object)val).name = name; return val; } public static GameObject Donor(string commaSeparated, out string chosen) { chosen = null; if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } string[] array = (commaSeparated ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if (!((Object)(object)prefab == (Object)null)) { chosen = text; return prefab; } } } return null; } public static bool Register(GameObject prefab) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || (Object)(object)prefab == (Object)null) { return false; } if ((Object)(object)instance.GetPrefab(((Object)prefab).name) != (Object)null) { return true; } if (!instance.m_prefabs.Contains(prefab)) { instance.m_prefabs.Add(prefab); } try { NamedPrefabs(instance)[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = prefab; } catch (Exception ex) { ComplainOnce("scene:" + ((Object)prefab).name, "Could not register " + ((Object)prefab).name + " with ZNetScene: " + ex.Message); return false; } Log.LogInfo((object)("Registered " + ((Object)prefab).name + " with ZNetScene.")); return true; } public static bool RegisterItem(GameObject prefab) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || (Object)(object)prefab == (Object)null) { return false; } if (instance.m_items == null || instance.m_items.Count == 0) { return false; } if ((Object)(object)instance.GetItemPrefab(((Object)prefab).name) != (Object)null) { return true; } if (!instance.m_items.Contains(prefab)) { instance.m_items.Add(prefab); } try { UpdateRegisters(instance); } catch (Exception ex) { ComplainOnce("db:" + ((Object)prefab).name, "Could not refresh ObjectDB for " + ((Object)prefab).name + ": " + ex.Message); return false; } Log.LogInfo((object)("Registered " + ((Object)prefab).name + " with ObjectDB.")); return true; } public static PieceTable ToolPieces(string toolPrefab) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrEmpty(toolPrefab)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(toolPrefab); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val == (Object)null || val.m_itemData == null || val.m_itemData.m_shared == null) { return null; } PieceTable buildPieces = val.m_itemData.m_shared.m_buildPieces; if (!((Object)(object)buildPieces != (Object)null) || buildPieces.m_pieces == null) { return null; } return buildPieces; } public static bool InTool(GameObject prefab, string toolPrefab) { PieceTable val = ToolPieces(toolPrefab); if ((Object)(object)val != (Object)null && (Object)(object)prefab != (Object)null) { return val.m_pieces.Contains(prefab); } return false; } public static bool AddToTool(GameObject prefab, string toolPrefab) { if ((Object)(object)prefab == (Object)null) { return false; } PieceTable val = ToolPieces(toolPrefab); if ((Object)(object)val == (Object)null) { return false; } if (val.m_pieces.Contains(prefab)) { return true; } val.m_pieces.Add(prefab); Log.LogInfo((object)(((Object)prefab).name + " added to the " + toolPrefab + ".")); return true; } private static void ComplainOnce(string key, string message) { if (Complained.Add(key)) { Log.LogError((object)message); } } private static Dictionary NamedPrefabs(ZNetScene scene) { if (_named == null) { _named = AccessTools.FieldRefAccess>("m_namedPrefabs"); } return _named.Invoke(scene); } private static void UpdateRegisters(ObjectDB db) { if (_updateRegisters == null) { _updateRegisters = AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null); } if (_updateRegisters == null) { throw new MissingMethodException("ObjectDB.UpdateRegisters"); } _updateRegisters.Invoke(db, null); } } } namespace Taum { [HarmonyPatch] internal static class Follow { private static bool Wants(Tameable tameable) { if (!TaumConfig.Enabled.Value || (Object)(object)tameable == (Object)null) { return false; } if (!tameable.IsTamed()) { return false; } return !tameable.m_commandable; } [HarmonyPrefix] [HarmonyPatch(typeof(Tameable), "Interact")] private static bool AltToFollow(Tameable __instance, Humanoid user, bool hold, bool alt, ref bool __result) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (hold || alt || !Wants(__instance)) { return true; } if (!Input.GetKey(TaumConfig.FollowKey.Value)) { return true; } __instance.Command(user, true); __result = true; return false; } [HarmonyPostfix] [HarmonyPatch(typeof(Tameable), "GetHoverText")] private static void Offer(Tameable __instance, ref string __result) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (Wants(__instance) && !string.IsNullOrEmpty(__result)) { string text = ((object)TaumConfig.FollowKey.Value/*cast due to .constrained prefix*/).ToString().Replace("Left", "").Replace("Right", ""); __result += Localization.instance.Localize("\n[" + text + " + $KEY_Use] Follow / stay"); } } } internal static class TaumConfig { internal static ConfigEntry Enabled; internal static ConfigEntry Verbose; internal static ConfigEntry FollowKey; internal static void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Taum", "Enabled", true, "Off leaves the plugin loaded and changing nothing."); Verbose = cfg.Bind("Taum", "Verbose", false, "Write each halter put on, taken off and dropped to BepInEx/LogOutput.log."); FollowKey = cfg.Bind("Taum", "FollowKey", (KeyCode)308, "Held with E on a tamed boar or hen to toggle follow/stay. Alt because vanilla already spends Shift+E on renaming and plain E on petting - all three gestures keep their own key."); } internal static Dictionary Table(string raw, char separator = ',') { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(raw)) { return dictionary; } string[] array = raw.Split(new char[1] { separator }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); int num = text.IndexOf(':'); if (num > 0 && num != text.Length - 1) { dictionary[text.Substring(0, num).Trim()] = text.Substring(num + 1).Trim(); } } return dictionary; } } internal static class TaumPatches { [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnSpawned")] private static void OnSpawned(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && TaumConfig.Enabled.Value && TaumConfig.Verbose.Value) { TaumPlugin.Log.LogInfo((object)"Player spawned - patches are live."); } } } [BepInPlugin("ezomic.valheim.taum", "Taum", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class TaumPlugin : BaseUnityPlugin { public const string PluginGuid = "ezomic.valheim.taum"; public const string PluginName = "Taum"; public const string PluginVersion = "1.0.0"; public const string PluginAuthor = "Robbin Thijssen"; private const string CoreGuid = "ezomic.valheim.core"; internal static ManualLogSource Log; private static readonly HashSet Said = new HashSet(); internal static bool CorePresent; private Harmony _harmony; internal static void LogOnce(string message) { if (Log != null && Said.Add(message)) { Log.LogWarning((object)message); } } private void Awake() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; TaumConfig.Bind(((BaseUnityPlugin)this).Config); Prefabs.Log = ((BaseUnityPlugin)this).Logger; TryRegisterWithCore(); _harmony = new Harmony("ezomic.valheim.taum"); _harmony.PatchAll(typeof(TaumPatches)); _harmony.PatchAll(typeof(Follow)); Log.LogInfo((object)"Taum 1.0.0 by Robbin Thijssen - ready."); } private void TryRegisterWithCore() { CorePresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core"); if (!CorePresent) { Log.LogInfo((object)"Core not installed - running standalone, without the version gate."); } else { RegisterWithCore(); } } [MethodImpl(MethodImplOptions.NoInlining)] private void RegisterWithCore() { Suite.Register("ezomic.valheim.taum", "Taum", "1.0.0", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null); Suite.Sync((ConfigEntryBase)(object)TaumConfig.Enabled); } private void Update() { if (TaumConfig.Enabled.Value) { Prefabs.Tick(); } } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } }