using System; using System.Collections; 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 Ezomic.Shared; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [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("Sound a horn in one of Hildir's dungeons and its mini-boss wakes again.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ed4086fb33547df831bf919c89c899dc9da769d4")] [assembly: AssemblyProduct("Lur")] [assembly: AssemblyTitle("Lur")] [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 Lur { internal static class Dungeons { private const Theme Trio = (Theme)3584; internal unsafe static DungeonGenerator Containing(Vector3 point) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) DungeonGenerator val = null; foreach (DungeonGenerator item in Live()) { if (Enabled(item.m_themes) && Inside(item, point)) { if ((Object)(object)val != (Object)null) { ManualLogSource log = LurPlugin.Log; Vector3 val2 = point; log.LogWarning((object)("Two dungeons overlap at " + ((object)(*(Vector3*)(&val2))/*cast due to .constrained prefix*/).ToString() + " - refusing to guess which.")); return null; } val = item; } } return val; } internal static IEnumerable Live() { Scene activeScene = SceneManager.GetActiveScene(); GameObject[] roots = ((Scene)(ref activeScene)).GetRootGameObjects(); DungeonGenerator val2 = default(DungeonGenerator); ZNetView val3 = default(ZNetView); foreach (GameObject val in roots) { if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref val2) && !((Object)(object)val2 == (Object)null) && val.TryGetComponent(ref val3) && !((Object)(object)val3 == (Object)null) && val3.IsValid()) { yield return val2; } } } internal static bool Enabled(Theme themes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0025: 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_0039: Unknown result type (might be due to invalid IL or missing references) if ((themes & 0xE00) == 0) { return false; } if ((themes & 0x200) != 0) { return LurConfig.WakeCrypt.Value; } if ((themes & 0x400) != 0) { return LurConfig.WakeCave.Value; } if ((themes & 0x800) != 0) { return LurConfig.WakeTower.Value; } return false; } internal static string Describe(DungeonGenerator dg) { if ((Object)(object)dg == (Object)null) { return ""; } return Utils.GetPrefabName(((Component)dg).gameObject) + " [" + ((object)Unsafe.As(ref dg.m_themes)/*cast due to .constrained prefix*/).ToString() + "]"; } internal static bool Inside(DungeonGenerator dg, Vector3 point) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)dg == (Object)null) { return false; } float num = Mathf.Max(0f, LurConfig.RoomPadding.Value); Room[] componentsInChildren = ((Component)dg).GetComponentsInChildren(); foreach (Room val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Vector3 val2 = Quaternion.Inverse(((Component)val).transform.rotation) * (point - ((Component)val).transform.position); Vector3 val3 = Vector3Int.op_Implicit(val.m_size) * 0.5f; if (Mathf.Abs(val2.x) <= val3.x + num && Mathf.Abs(val2.y) <= val3.y + num && Mathf.Abs(val2.z) <= val3.z + num) { return true; } } } if (!LurConfig.UseLocationRadiusFallback.Value) { return false; } Location location = Location.GetLocation(((Component)dg).transform.position, true); if ((Object)(object)location == (Object)null) { return false; } return location.IsInside(point, 0f, false); } internal static bool Ready(DungeonGenerator dg) { if ((Object)(object)dg == (Object)null) { return false; } return ((Component)dg).GetComponentsInChildren().Length != 0; } internal static List Spawners(DungeonGenerator dg) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if ((Object)(object)dg == (Object)null) { return list; } Scene activeScene = SceneManager.GetActiveScene(); GameObject[] rootGameObjects = ((Scene)(ref activeScene)).GetRootGameObjects(); CreatureSpawner val2 = default(CreatureSpawner); ZNetView val3 = default(ZNetView); foreach (GameObject val in rootGameObjects) { if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref val2) && !((Object)(object)val2 == (Object)null) && val.TryGetComponent(ref val3) && !((Object)(object)val3 == (Object)null) && val3.IsValid() && Inside(dg, val.transform.position)) { list.Add(val2); } } return list; } internal static List BossSpawners(DungeonGenerator dg) { List list = new List(); foreach (CreatureSpawner item in Spawners(dg)) { if (IsBoss(item)) { list.Add(item); } } return list; } internal static CreatureSpawner Boss(DungeonGenerator dg) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (CreatureSpawner item in Spawners(dg)) { if (IsBoss(item)) { list.Add(item); } } if (list.Count == 0) { return null; } if (list.Count == 1) { return list[0]; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return list[0]; } CreatureSpawner result = list[0]; float num = float.MaxValue; foreach (CreatureSpawner item2 in list) { float num2 = Vector3.Distance(((Component)localPlayer).transform.position, ((Component)item2).transform.position); if (!(num2 >= num)) { num = num2; result = item2; } } return result; } internal static bool IsBoss(CreatureSpawner spawner) { if ((Object)(object)spawner == (Object)null) { return false; } if ((Object)(object)spawner.m_creaturePrefab == (Object)null) { return false; } Character val = default(Character); if (!spawner.m_creaturePrefab.TryGetComponent(ref val)) { return false; } if ((Object)(object)val == (Object)null) { return false; } string defeatSetGlobalKey = val.m_defeatSetGlobalKey; if (string.IsNullOrEmpty(defeatSetGlobalKey)) { return false; } if (val.m_boss) { return true; } return !VanillaKey(defeatSetGlobalKey); } private static bool VanillaKey(string key) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) try { GlobalKeys result; return Enum.TryParse(key, ignoreCase: true, out result) && Enum.IsDefined(typeof(GlobalKeys), result); } catch { return false; } } internal static List Groupmates(CreatureSpawner boss, List candidates) { //IL_0071: 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) List list = new List(); if ((Object)(object)boss == (Object)null || candidates == null) { return list; } if (boss.m_spawnGroupRadius <= 0f || boss.m_maxGroupSpawned < 1) { return list; } foreach (CreatureSpawner candidate in candidates) { if (!((Object)(object)candidate == (Object)null) && !((Object)(object)candidate == (Object)(object)boss) && candidate.m_spawnGroupID == boss.m_spawnGroupID) { float num = boss.m_spawnGroupRadius + candidate.m_spawnGroupRadius; if (!(Vector3.Distance(((Component)boss).transform.position, ((Component)candidate).transform.position) > num)) { list.Add(candidate); } } } return list; } internal static bool HoldsTombstone(DungeonGenerator dg) { //IL_0000: 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) //IL_003a: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); GameObject[] rootGameObjects = ((Scene)(ref activeScene)).GetRootGameObjects(); TombStone val2 = default(TombStone); foreach (GameObject val in rootGameObjects) { if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref val2) && !((Object)(object)val2 == (Object)null) && Inside(dg, val.transform.position)) { return true; } } return false; } } internal static class Icons { public static Sprite Load(string fileName, string label) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(fileName)) { return null; } string path = Path.Combine(Path.GetDirectoryName(typeof(Icons).Assembly.Location), fileName); if (!File.Exists(path)) { LurPlugin.LogOnce("No " + fileName + " beside the dll - " + label + " will use the donor's icon, which is someone else's picture."); return null; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; if (!LoadPng(val, File.ReadAllBytes(path))) { return null; } ((Object)val).name = label + "_icon"; ((Object)val).hideFlags = (HideFlags)61; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { LurPlugin.Log.LogError((object)("Could not read " + fileName + ": " + ex.Message)); return null; } } public static string For(string modelFile) { if (string.IsNullOrEmpty(modelFile)) { return null; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(modelFile); if (!string.IsNullOrEmpty(fileNameWithoutExtension)) { return fileNameWithoutExtension + "_icon.png"; } return null; } private static bool LoadPng(Texture2D texture, byte[] data) { Type type = AccessTools.TypeByName("UnityEngine.ImageConversion"); if (type == null) { LurPlugin.LogOnce("UnityEngine.ImageConversion is missing - cannot read icons."); return false; } MethodInfo methodInfo = AccessTools.Method(type, "LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) }, (Type[])null) ?? AccessTools.Method(type, "LoadImage", new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, (Type[])null); if (methodInfo == null) { LurPlugin.LogOnce("No LoadImage overload found on UnityEngine.ImageConversion."); return false; } object[] parameters = ((methodInfo.GetParameters().Length != 3) ? new object[2] { texture, data } : new object[3] { texture, data, false }); return (bool)methodInfo.Invoke(null, parameters); } } internal static class LurConfig { internal static ConfigEntry Enabled; internal static ConfigEntry WakeCrypt; internal static ConfigEntry WakeCave; internal static ConfigEntry WakeTower; internal static ConfigEntry CooldownDays; internal static ConfigEntry RoomPadding; internal static ConfigEntry WatchSeconds; internal static ConfigEntry UseLocationRadiusFallback; internal static ConfigEntry Price; internal static ConfigEntry Stack; internal static ConfigEntry SoldAfterKey; internal static ConfigEntry ItemName; internal static ConfigEntry ItemDonor; internal static ConfigEntry SoundEffectPrefab; internal static ConfigEntry Diagnose; internal static ConfigEntry Verbose; internal static void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Lur", "Enabled", true, "Off leaves the plugin loaded and changing nothing. The horn still exists in any inventory that holds one; sounding it simply does nothing."); ItemName = cfg.Bind("Lur", "ItemName", "Lur", "The horn's display name. This is the label only. The prefab name is fixed in code and can never change: ZNetScene keys on it and discards the ZDO of anything it cannot resolve, so a rename would delete every horn already sitting in a chest, silently."); WakeCrypt = cfg.Bind("Dungeons", "WakeCrypt", true, "Wake the Smouldering Tomb (Hildir's Black Forest crypt)."); WakeCave = cfg.Bind("Dungeons", "WakeCave", true, "Wake the Howling Cavern (Hildir's mountain cave)."); WakeTower = cfg.Bind("Dungeons", "WakeTower", true, "Wake the Sealed Tower (Hildir's plains fortress). This one is a ground location rather than a sky interior. It needs no separate code path - Lur deletes nothing and so has no height test anywhere - but it is the one of the three least proven in play."); CooldownDays = cfg.Bind("Dungeons", "CooldownDays", 5f, "In-game days before the same dungeon can be woken again. 0 leaves the horn's price as the only cost. Measured on the world clock, so time spent with nobody online does not count."); RoomPadding = cfg.Bind("Dungeons", "RoomPadding", 1f, "Metres of slack added to each room's box when deciding what is inside the dungeon. Raise it if a boss refuses to wake and the log shows its spawner just outside."); UseLocationRadiusFallback = cfg.Bind("Dungeons", "UseLocationRadiusFallback", false, "Also count anything inside the Location's own radius as part of the dungeon, not just the generator's rooms. Off by default because it is looser and can reach a spawner standing outside the tower wall. Turn it on only if the diagnostic log shows the tower's boss spawner is not in any room - that is the single case it exists for."); WatchSeconds = cfg.Bind("Dungeons", "WatchSeconds", 30f, "How long to wait for something to actually spawn before giving up and putting the spawners back as they were. On a timeout the horn is not consumed. Must comfortably exceed the spawners' own interval."); Price = cfg.Bind("Store", "Price", 500, "What Hildir charges, in coins. Her own stock runs a few hundred to somewhat over a thousand, so this sits mid-range. Together with CooldownDays it is the entire cost of a rematch."); Stack = cfg.Bind("Store", "Stack", 1, "How many horns one purchase buys. Vanilla clamps this to the item's own max stack size at the till."); SoldAfterKey = cfg.Bind("Store", "SoldAfterKey", "", "A global key Hildir requires before she will stock the horn. Empty means always on sale, which is the recommended setting: sounding it already refuses in a dungeon whose boss is still alive. 'bosshildir1' is the value for a host who wants the shop gated as well. An unknown key is not an error - the row simply never appears, so a typo reads as 'she does not sell it'."); ItemDonor = cfg.Bind("Lur", "ItemDonor", "SurtlingCore", "Vanilla item to take the horn's machinery from. Its appearance is not used."); SoundEffectPrefab = cfg.Bind("Lur", "SoundEffectPrefab", "", "Name of a vanilla effect prefab to play where the horn is sounded. Empty for none. Resolved through ZNetScene; a name that does not exist is logged and skipped, so a rename in a game update costs the noise and nothing else."); Diagnose = cfg.Bind("Diagnostics", "Diagnose", false, "Log a full census of every Hildir dungeon as it loads: its generator, its themes, its rooms, and every spawner near it with an inside-or-outside verdict. This is how the open questions about the Sealed Tower get answered, and it is the first thing to turn on if the horn does nothing."); Verbose = cfg.Bind("Diagnostics", "Verbose", false, "Write what was found and what was changed to BepInEx/LogOutput.log. Note that exceptions land in Player.log instead, not here."); } } internal static class LurItem { public const string Name = "Lur"; private const string Mesh = "lur.obj"; private const string Icon = "lur.png"; internal static GameObject Build() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) ModelData modelData = ObjMesh.Load(Path.Combine(Path.GetDirectoryName(typeof(LurItem).Assembly.Location), "lur.obj")); if (modelData == null) { LurPlugin.Log.LogError((object)"No lur.obj beside the dll - cannot build Lur. The mod will keep retrying, and Hildir will not stock the horn until it succeeds."); return null; } GameObject val = Donor(); if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = Prefabs.Clone(val, "Lur"); if ((Object)(object)val2 == (Object)null) { return null; } val2.transform.localRotation = Quaternion.identity; Visual(val2, modelData); ItemDrop component = val2.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { LurPlugin.Log.LogError((object)("Donor " + ((Object)val).name + " has no usable ItemDrop.")); return null; } SharedData shared = component.m_itemData.m_shared; shared.m_name = LurConfig.ItemName.Value; shared.m_description = "A coiled horn of bone and iron, worn smooth at the mouth."; shared.m_itemType = (ItemType)1; shared.m_maxStackSize = 10; shared.m_weight = 1f; shared.m_teleportable = true; shared.m_questItem = false; Sprite val3 = Icons.Load("lur.png", "Lur"); if ((Object)(object)val3 != (Object)null) { shared.m_icons = (Sprite[])(object)new Sprite[1] { val3 }; } component.m_itemData.m_stack = 1; component.m_itemData.m_dropPrefab = val2; LurPlugin.Log.LogInfo((object)("Built Lur from " + ((Object)val).name + ".")); return val2; } private static GameObject Donor() { ZNetScene instance = ZNetScene.instance; string[] array = new string[3] { LurConfig.ItemDonor.Value, "SurtlingCore", "Wood" }; foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { GameObject prefab = instance.GetPrefab(text); if ((Object)(object)prefab != (Object)null) { return prefab; } LurPlugin.LogOnce("Item donor '" + text + "' does not exist."); } } return null; } private static void Visual(GameObject clone, ModelData model) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) MeshRenderer[] componentsInChildren = clone.GetComponentsInChildren(true); foreach (MeshRenderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { MeshFilter component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } Object.DestroyImmediate((Object)(object)val); } } Light[] componentsInChildren2 = clone.GetComponentsInChildren(true); foreach (Light val2 in componentsInChildren2) { if ((Object)(object)val2 != (Object)null) { Object.DestroyImmediate((Object)(object)val2); } } ParticleSystem[] componentsInChildren3 = clone.GetComponentsInChildren(true); foreach (ParticleSystem val3 in componentsInChildren3) { if ((Object)(object)val3 != (Object)null) { Object.DestroyImmediate((Object)(object)val3); } } ParticleSystemRenderer[] componentsInChildren4 = clone.GetComponentsInChildren(true); foreach (ParticleSystemRenderer val4 in componentsInChildren4) { if ((Object)(object)val4 != (Object)null) { Object.DestroyImmediate((Object)(object)val4); } } GameObject val5 = new GameObject("lur_visual"); val5.transform.SetParent(clone.transform, false); val5.AddComponent().sharedMesh = model.Mesh; ((Renderer)val5.AddComponent()).sharedMaterials = Skins.Skin(model.Groups); Skins.Remap(model.Mesh, model.Groups); Ground(clone, model); } private static void Ground(GameObject clone, ModelData model) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)clone.GetComponentInChildren(true) != (Object)null) && !((Object)(object)model.Mesh == (Object)null)) { Bounds bounds = model.Mesh.bounds; BoxCollider obj = clone.AddComponent(); obj.center = ((Bounds)(ref bounds)).center; obj.size = ((Bounds)(ref bounds)).size; LurPlugin.LogOnce("Donor left no collider on Lur - added one from the mesh bounds so it does not fall through the world."); } } } internal static class LurPatches { [HarmonyPrefix] [HarmonyPatch(typeof(Humanoid), "UseItem")] private static bool UseItem(Humanoid __instance, ItemData item, bool fromInventoryGui) { try { if (!LurConfig.Enabled.Value) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (fromInventoryGui) { return true; } if (item == null || (Object)(object)item.m_dropPrefab == (Object)null) { return true; } if (((Object)item.m_dropPrefab).name != "Lur") { return true; } Sounding.Sound(Player.m_localPlayer); return false; } catch (Exception ex) { LurPlugin.Log.LogError((object)("Sounding the horn threw, so it did nothing: " + ex)); return true; } } [HarmonyPostfix] [HarmonyPatch(typeof(DungeonGenerator), "Awake")] private static void GeneratorAwake(DungeonGenerator __instance) { try { if (!((Object)(object)__instance == (Object)null)) { ((MonoBehaviour)__instance).StartCoroutine(Census(__instance)); } } catch (Exception ex) { LurPlugin.Log.LogError((object)("Dungeon census threw: " + ex)); } } private static IEnumerator Census(DungeonGenerator dg) { yield return (object)new WaitForSeconds(5f); if ((Object)(object)dg == (Object)null || (dg.m_themes & 0xE00) == 0) { yield break; } Room[] componentsInChildren = ((Component)dg).GetComponentsInChildren(); Vector3 position = ((Component)dg).transform.position; LurPlugin.Log.LogInfo((object)$"Hildir dungeon: {Utils.GetPrefabName(((Component)dg).gameObject)} themes={dg.m_themes} at {position} interior={Character.InInterior(position)} rooms={componentsInChildren.Length} enabled={Dungeons.Enabled(dg.m_themes)}"); if (!LurConfig.Diagnose.Value) { yield break; } for (int i = 0; i < componentsInChildren.Length; i++) { Room val = componentsInChildren[i]; if (!((Object)(object)val == (Object)null)) { LurPlugin.Log.LogInfo((object)$" room {i}: {((Object)val).name} size={val.m_size} at {((Component)val).transform.position}"); } } List list = Dungeons.Spawners(dg); LurPlugin.Log.LogInfo((object)(" spawners inside: " + list.Count)); foreach (CreatureSpawner item in list) { ZNetView component = ((Component)item).GetComponent(); ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); LurPlugin.Log.LogInfo((object)string.Format(" {0} respawn={1} trigger={2} day={3} night={4} req='{5}' block='{6}' group='{7}' connection={8}", Utils.GetPrefabName(((Component)item).gameObject), item.m_respawnTimeMinuts, item.m_triggerDistance, item.m_spawnAtDay, item.m_spawnAtNight, item.m_requiredGlobalKey, item.m_blockingGlobalKey, item.m_spawnGroupID, (val2 != null) ? ((object)val2.GetConnectionType()/*cast due to .constrained prefix*/).ToString() : "")); } } [HarmonyPostfix] [HarmonyPatch(typeof(Trader), "GetAvailableItems")] private static void AvailableItems(Trader __instance, List __result) { try { Store.Offer(__instance, __result); } catch (Exception ex) { LurPlugin.Log.LogError((object)("Offering the horn threw, so it is not stocked: " + ex)); } } } [BepInPlugin("ezomic.valheim.lur", "Lur", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class LurPlugin : BaseUnityPlugin { public const string PluginGuid = "ezomic.valheim.lur"; public const string PluginName = "Lur"; public const string PluginVersion = "1.0.0"; public const string PluginAuthor = "Robbin Thijssen"; private const string CoreGuid = "ezomic.valheim.core"; internal static ManualLogSource Log; internal static bool CorePresent; private Harmony _harmony; private static readonly HashSet Said = new HashSet(); private void Awake() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; LurConfig.Bind(((BaseUnityPlugin)this).Config); TryRegisterWithCore(); Prefabs.Log = ((BaseUnityPlugin)this).Logger; Prefabs.Keep("Lur", LurItem.Build, item: true); _harmony = new Harmony("ezomic.valheim.lur"); _harmony.PatchAll(typeof(LurPatches)); Log.LogInfo((object)"Lur 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.lur", "Lur", "1.0.0", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null); Suite.Sync((ConfigEntryBase[])(object)new ConfigEntryBase[11] { (ConfigEntryBase)LurConfig.Enabled, (ConfigEntryBase)LurConfig.WakeCrypt, (ConfigEntryBase)LurConfig.WakeCave, (ConfigEntryBase)LurConfig.WakeTower, (ConfigEntryBase)LurConfig.CooldownDays, (ConfigEntryBase)LurConfig.RoomPadding, (ConfigEntryBase)LurConfig.WatchSeconds, (ConfigEntryBase)LurConfig.UseLocationRadiusFallback, (ConfigEntryBase)LurConfig.Price, (ConfigEntryBase)LurConfig.Stack, (ConfigEntryBase)LurConfig.SoldAfterKey }); Suite.Local((ConfigEntryBase[])(object)new ConfigEntryBase[2] { (ConfigEntryBase)LurConfig.Diagnose, (ConfigEntryBase)LurConfig.Verbose }); } private void Update() { Prefabs.Tick(); } internal static void LogOnce(string message) { if (Said.Add(message)) { Log.LogWarning((object)message); } } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } internal sealed class ModelData { public Mesh Mesh; public string[] Groups; } internal static class ObjMesh { public static ModelData Load(string path) { if (!File.Exists(path)) { LurPlugin.Log.LogWarning((object)("No model at " + path)); return null; } try { return Parse(File.ReadAllLines(path), Path.GetFileNameWithoutExtension(path)); } catch (Exception ex) { LurPlugin.Log.LogError((object)("Could not read " + path + ": " + ex.Message)); return null; } } private static ModelData Parse(string[] lines, string name) { //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); List list3 = new List(); List list4 = new List(); List list5 = new List(); List> list6 = new List>(); Dictionary index = new Dictionary(); Dictionary lookup = new Dictionary(); List list7 = new List(); List list8 = new List(); List list9 = new List(); List outShades = new List(); CultureInfo invariantCulture = CultureInfo.InvariantCulture; List triangles = Bucket("", list5, list6, index); for (int i = 0; i < lines.Length; i++) { string text = lines[i].Trim(); if (text.Length == 0 || text[0] == '#') { continue; } string[] array = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { continue; } switch (array[0]) { case "v": if (array.Length >= 4) { list.Add(new Vector3(float.Parse(array[1], invariantCulture), float.Parse(array[2], invariantCulture), float.Parse(array[3], invariantCulture))); if (array.Length >= 7) { list4.Add(new Color(float.Parse(array[4], invariantCulture), float.Parse(array[5], invariantCulture), float.Parse(array[6], invariantCulture), 1f)); } } break; case "vt": if (array.Length >= 3) { list2.Add(new Vector2(float.Parse(array[1], invariantCulture), float.Parse(array[2], invariantCulture))); } break; case "vn": if (array.Length >= 4) { list3.Add(new Vector3(float.Parse(array[1], invariantCulture), float.Parse(array[2], invariantCulture), float.Parse(array[3], invariantCulture))); } break; case "usemtl": triangles = Bucket(array[1].Trim(), list5, list6, index); break; case "f": AddFace(array, list, list2, list3, list4, lookup, list7, list8, list9, outShades, triangles); break; } } for (int num = list6.Count - 1; num >= 0; num--) { if (list6[num].Count == 0) { list6.RemoveAt(num); list5.RemoveAt(num); } } if (list7.Count == 0 || list6.Count == 0) { LurPlugin.Log.LogError((object)("Model " + name + " has no geometry.")); return null; } Mesh val = new Mesh { name = name }; if (list7.Count > 65535) { val.indexFormat = (IndexFormat)1; LurPlugin.Log.LogInfo((object)(name + " needs 32 bit indices (" + list7.Count + " verts).")); } val.SetVertices(list7); if (list8.Count == list7.Count) { val.SetUVs(0, list8); } val.subMeshCount = list6.Count; for (int j = 0; j < list6.Count; j++) { val.SetTriangles(list6[j], j); } if (list9.Count == list7.Count) { val.SetNormals(list9); } else { val.RecalculateNormals(); } Color[] array2 = (Color[])(object)new Color[list7.Count]; for (int k = 0; k < array2.Length; k++) { array2[k] = Color.white; } val.colors = array2; val.RecalculateBounds(); val.RecalculateTangents(); int num2 = 0; for (int l = 0; l < list6.Count; l++) { num2 += list6[l].Count; } LurPlugin.Log.LogInfo((object)string.Format("Loaded {0}: {1} verts, {2} tris, parts [{3}]", name, list7.Count, num2 / 3, string.Join(", ", list5.ToArray()))); return new ModelData { Mesh = val, Groups = list5.ToArray() }; } private static List Bucket(string material, List names, List> buckets, Dictionary index) { if (index.TryGetValue(material, out var value)) { return buckets[value]; } index[material] = buckets.Count; names.Add(material); buckets.Add(new List()); return buckets[buckets.Count - 1]; } private static void AddFace(string[] parts, List positions, List uvs, List normals, List shades, Dictionary lookup, List outPositions, List outUvs, List outNormals, List outShades, List triangles) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) List list = new List(parts.Length - 1); for (int i = 1; i < parts.Length; i++) { string text = parts[i]; if (!lookup.TryGetValue(text, out var value)) { string[] fields = text.Split(new char[1] { '/' }); int num = Index(fields, 0, positions.Count); if (num < 0) { continue; } outPositions.Add(positions[num]); outShades.Add((num < shades.Count) ? shades[num] : Color.white); int num2 = Index(fields, 1, uvs.Count); outUvs.Add((num2 >= 0) ? uvs[num2] : Vector2.zero); int num3 = Index(fields, 2, normals.Count); outNormals.Add((num3 >= 0) ? normals[num3] : Vector3.up); value = (lookup[text] = outPositions.Count - 1); } list.Add(value); } for (int j = 2; j < list.Count; j++) { triangles.Add(list[0]); triangles.Add(list[j - 1]); triangles.Add(list[j]); } } private static int Index(string[] fields, int slot, int count) { if (slot >= fields.Length) { return -1; } string text = fields[slot]; if (string.IsNullOrEmpty(text)) { return -1; } if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return -1; } if (result > 0) { result--; } else if (result < 0) { result = count + result; } if (result < 0 || result >= count) { return -1; } return result; } } internal static class PropIndex { private static Dictionary _index; private static readonly string[] Chaff = new string[5] { "destruction", "broken", "lod", "vfx", "sfx" }; public static GameObject Find(string name) { if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(name); if ((Object)(object)prefab != (Object)null) { return prefab; } } if (_index == null) { BuildIndex(); } if (!_index.TryGetValue(name, out var value)) { return null; } return value; } private static void BuildIndex() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) _index = new Dictionary(StringComparer.OrdinalIgnoreCase); GameObject[] array = Resources.FindObjectsOfTypeAll(); foreach (GameObject val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.transform.parent != (Object)null || (Object)(object)val.GetComponentInChildren(true) == (Object)null) { continue; } Scene scene = val.scene; bool flag = ((Scene)(ref scene)).IsValid(); if (_index.TryGetValue(((Object)val).name, out var value)) { if (flag) { continue; } scene = value.scene; if (!((Scene)(ref scene)).IsValid()) { continue; } } _index[((Object)val).name] = val; } LurPlugin.Log.LogInfo((object)("Prop index built: " + _index.Count + " candidates with meshes.")); } public static void Search(string keywords) { if (string.IsNullOrEmpty(keywords)) { return; } if (_index == null) { BuildIndex(); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string key in _index.Keys) { hashSet.Add(key); } if ((Object)(object)ZNetScene.instance != (Object)null) { foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab != (Object)null) { hashSet.Add(((Object)prefab).name); } } } string[] array = keywords.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } List list = new List(); foreach (string item in hashSet) { if (item.IndexOf(text, StringComparison.OrdinalIgnoreCase) < 0) { continue; } string text2 = item.ToLowerInvariant(); string text3 = text.ToLowerInvariant(); bool flag = false; string[] chaff = Chaff; foreach (string value in chaff) { if (text2.Contains(value) && !text3.Contains(value)) { flag = true; } } if (!flag) { list.Add(item); } } list.Sort(); LurPlugin.Log.LogInfo((object)("Prefabs matching '" + text + "' (" + list.Count + "): " + string.Join(", ", list.GetRange(0, Math.Min(40, list.Count)).ToArray()))); } } } internal static class Skins { private const string GlowGroup = "core"; private const string Emission = "_EmissionColor"; private static readonly Dictionary Donors = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "bark", new string[3] { "wood_wall", "wood_beam", "piece_chest_wood" } }, { "wood", new string[3] { "wood_wall", "wood_beam", "piece_chest_wood" } }, { "seed", new string[2] { "wood_beam", "wood_wall" } }, { "stone", new string[3] { "stone_wall_2x1", "piece_stonecutter", "smelter" } }, { "bone", new string[3] { "BoneFragments", "TrophyDeer", "wood_beam" } }, { "iron", new string[3] { "Iron", "IronScrap", "stone_wall_2x1" } }, { "moss", new string[5] { "sapling_carrot", "sapling_turnip", "Bush01", "shrub_2", "piece_beehive" } }, { "cap", new string[4] { "Pickable_Mushroom", "Pickable_Mushroom_yellow", "Pickable_Mushroom_blue", "sapling_carrot" } }, { "bloom", new string[2] { "Pickable_Dandelion", "sapling_carrot" } }, { "cloth", new string[4] { "rug_lox", "rug_deer", "rug_fur", "wood_wall" } }, { "meal", new string[2] { "BoneFragments", "stone_wall_2x1" } }, { "rope", new string[2] { "wood_wall", "wood_beam" } }, { "bud", new string[3] { "Pickable_Thistle", "Pickable_Dandelion", "sapling_carrot" } } }; private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary Atlas = new Dictionary(StringComparer.OrdinalIgnoreCase); public static void Invalidate() { Cache.Clear(); Atlas.Clear(); } public static Material For(string group) { if (Cache.TryGetValue(group, out var value)) { return value; } if (group == "core") { Material val = Pick(group, mustEmit: true); if ((Object)(object)val != (Object)null) { return val; } } return Pick(group, mustEmit: false); } private static Material Pick(string group, bool mustEmit) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) string[] array = DonorsFor(group); foreach (string text in array) { GameObject val = PropIndex.Find(text); if ((Object)(object)val == (Object)null) { continue; } MeshRenderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (MeshRenderer val2 in componentsInChildren) { Material sharedMaterial = ((Renderer)val2).sharedMaterial; if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null) && sharedMaterial.HasProperty("_MainTex") && !((Object)(object)sharedMaterial.GetTexture("_MainTex") == (Object)null) && (!mustEmit || sharedMaterial.HasProperty("_EmissionColor"))) { Cache[group] = sharedMaterial; Atlas[group] = UvRegion((Renderer)(object)val2); LurPlugin.Log.LogInfo((object)string.Format("'{0}' skinned with {1} from {2} (shader {3}){4}, atlas {5}.", group, ((Object)sharedMaterial).name, text, ((Object)sharedMaterial.shader).name, mustEmit ? " [emissive]" : "", Atlas[group])); return sharedMaterial; } } } if (mustEmit) { return null; } LurPlugin.Log.LogWarning((object)("No material found for group '" + group + "'.")); Cache[group] = null; return null; } private static string[] DonorsFor(string group) { if (!Donors.TryGetValue(group, out var value)) { return Donors["wood"]; } return value; } public static Material[] Skin(string[] groups) { Material[] array = (Material[])(object)new Material[groups.Length]; for (int i = 0; i < groups.Length; i++) { array[i] = For(groups[i]); } return array; } private static Rect UvRegion(Renderer renderer) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, 0f, 1f, 1f); MeshFilter val2 = (((Object)(object)renderer != (Object)null) ? ((Component)renderer).GetComponent() : null); Mesh val3 = (((Object)(object)val2 != (Object)null) ? val2.sharedMesh : null); if ((Object)(object)val3 == (Object)null) { return val; } Vector2[] uv; int[] triangles; try { if (!val3.isReadable) { return val; } uv = val3.uv; triangles = val3.triangles; } catch { return val; } if (uv == null || uv.Length == 0 || triangles == null || triangles.Length < 3) { return val; } float num = 0f; Rect result = val; for (int i = 0; i + 2 < triangles.Length; i += 3) { int num2 = triangles[i]; int num3 = triangles[i + 1]; int num4 = triangles[i + 2]; if (num2 >= uv.Length || num3 >= uv.Length || num4 >= uv.Length) { continue; } float num5 = Mathf.Min(uv[num2].x, Mathf.Min(uv[num3].x, uv[num4].x)); float num6 = Mathf.Max(uv[num2].x, Mathf.Max(uv[num3].x, uv[num4].x)); float num7 = Mathf.Min(uv[num2].y, Mathf.Min(uv[num3].y, uv[num4].y)); float num8 = Mathf.Max(uv[num2].y, Mathf.Max(uv[num3].y, uv[num4].y)); float num9 = num6 - num5; float num10 = num8 - num7; if (!(num9 <= 0.005f) && !(num10 <= 0.005f) && !(num9 > 1f) && !(num10 > 1f)) { float num11 = num9 * num10; if (!(num11 <= num)) { num = num11; ((Rect)(ref result))..ctor(num5, num7, num9, num10); } } } if (!(num > 0f)) { return val; } return result; } public static void Remap(Mesh mesh, string[] groups) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mesh == (Object)null || groups == null) { return; } Vector2[] uv = mesh.uv; if (uv == null || uv.Length == 0) { return; } int num = Mathf.Min(groups.Length, mesh.subMeshCount); bool[] array = new bool[uv.Length]; for (int i = 0; i < num; i++) { if (!Atlas.TryGetValue(groups[i], out var value) || (((Rect)(ref value)).width >= 0.999f && ((Rect)(ref value)).height >= 0.999f)) { continue; } int[] triangles = mesh.GetTriangles(i); foreach (int num2 in triangles) { if (num2 >= 0 && num2 < uv.Length && !array[num2]) { array[num2] = true; uv[num2] = new Vector2(((Rect)(ref value)).x + Mathf.Clamp01(uv[num2].x) * ((Rect)(ref value)).width, ((Rect)(ref value)).y + Mathf.Clamp01(uv[num2].y) * ((Rect)(ref value)).height); } } } mesh.uv = uv; } } internal static class Sounding { internal struct Cleared { internal ZDO Zdo; internal ConnectionType Type; internal ZDOID Target; } internal static readonly int SoundedKey = StringExtensionMethods.GetStableHashCode("ezomic_lur_sounded"); internal static bool Sound(Player player) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return false; } Vector3 position = ((Component)player).transform.position; DungeonGenerator val = Dungeons.Containing(position); if ((Object)(object)val == (Object)null) { Refuse(player, "Nothing sleeps here."); return false; } if (!Dungeons.Ready(val)) { Refuse(player, "The stone is still settling."); return false; } ZNetView component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { Refuse(player, "Nothing sleeps here."); return false; } ZDO zDO = component.GetZDO(); if (zDO == null) { Refuse(player, "Nothing sleeps here."); return false; } if (!OffCooldown(zDO, player)) { return false; } if (Dungeons.HoldsTombstone(val)) { Refuse(player, "Someone lies unclaimed here."); return false; } List list = Wake(val, player); if (list.Count == 0) { return false; } CreatureSpawner val2 = Dungeons.Boss(val); GameObject val3 = (((Object)(object)val2 != (Object)null) ? val2.m_creaturePrefab : null); ManualLogSource log = LurPlugin.Log; string[] obj = new string[11] { "Sounded in ", Dungeons.Describe(val), " - re-armed ", list.Count.ToString(), " spawner(s), boss spawner ", ((Object)(object)val2 != (Object)null) ? Utils.GetPrefabName(((Component)val2).gameObject) : "", " at ", null, null, null, null }; object obj2; if (!((Object)(object)val2 != (Object)null)) { obj2 = "-"; } else { Vector3 position2 = ((Component)val2).transform.position; obj2 = ((Vector3)(ref position2)).ToString("F1"); } obj[7] = (string)obj2; obj[8] = " holding "; obj[9] = (((Object)(object)val3 != (Object)null) ? ((Object)val3).name : ""); obj[10] = ", watching."; log.LogInfo((object)string.Concat(obj)); Effect(position); Watch watch = ((Component)val).gameObject.AddComponent(); watch.Observe = Observable(Dungeons.BossSpawners(val), Dungeons.Spawners(val)); watch.Begin(list, zDO, player, ((Object)(object)val3 != (Object)null) ? StringExtensionMethods.GetStableHashCode(((Object)val3).name) : 0); return true; } private static bool OffCooldown(ZDO generator, Player player) { float num = Mathf.Max(0f, LurConfig.CooldownDays.Value); if (num <= 0f) { return true; } long num2 = generator.GetLong(SoundedKey, 0L); if (num2 == 0L) { return true; } long num3 = (long)ZNet.instance.GetTimeSeconds(); long num4 = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.m_dayLengthSec : 1200); long num5 = (long)(num * (float)num4) - (num3 - num2); if (num5 <= 0) { return true; } long num6 = (num5 + num4 - 1) / num4; Refuse(player, (num6 <= 1) ? "This place has not settled. Not long now." : ("This place has not settled. " + num6 + " days yet.")); return false; } private static List Wake(DungeonGenerator dg, Player player) { //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = Dungeons.Spawners(dg); List list3 = Dungeons.BossSpawners(dg); if ((Object)(object)Dungeons.Boss(dg) == (Object)null) { LurPlugin.Log.LogWarning((object)(Dungeons.Describe(dg) + ": no boss spawner found among " + list2.Count + " spawner(s). Turn on Diagnose and revisit to see what each of them holds.")); Refuse(player, "Nothing sleeps here."); return list; } List list4 = new List(); foreach (CreatureSpawner item in list3) { if (!list4.Contains(item)) { list4.Add(item); } foreach (CreatureSpawner item2 in Dungeons.Groupmates(item, list2)) { if (!list4.Contains(item2)) { list4.Add(item2); } } } string text = "Nothing sleeps here."; bool flag = false; foreach (CreatureSpawner item3 in list4) { if (!Clearable(item3, out var refusal)) { text = refusal; continue; } flag = true; break; } if (!flag) { LurPlugin.Log.LogInfo((object)(Dungeons.Describe(dg) + ": none of " + list4.Count + " boss/group spawner(s) can be cleared - " + text)); Refuse(player, text); return list; } foreach (CreatureSpawner item4 in list4) { ZDO val = ZdoOf(item4); if (val != null) { ConnectionType connectionType = val.GetConnectionType(); ZDOID connectionZDOID = val.GetConnectionZDOID((ConnectionType)3); if (Clearable(item4, out var _)) { val.SetOwner(ZDOMan.GetSessionID()); val.Set(ZDOVars.s_aliveTime, 0L); val.SetConnection((ConnectionType)0, ZDOID.None); list.Add(new Cleared { Zdo = val, Type = connectionType, Target = connectionZDOID }); } } } LurPlugin.Log.LogInfo((object)$"{Dungeons.Describe(dg)}: {list2.Count} spawner(s) present, {list3.Count} hold a boss, {list4.Count} in the group, cleared {list.Count}."); if (list.Count == 0) { Refuse(player, "Nothing sleeps here."); } return list; } private static bool Clearable(CreatureSpawner spawner, out string refusal) { //IL_0014: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) refusal = "Nothing sleeps here."; ZDO val = ZdoOf(spawner); if (val == null) { return false; } if ((int)val.GetConnectionType() == 0) { refusal = "It is already awake."; return false; } ZDOID connectionZDOID = val.GetConnectionZDOID((ConnectionType)3); if (!((ZDOID)(ref connectionZDOID)).IsNone() && ZDOMan.instance.GetZDO(connectionZDOID) != null) { refusal = "It is already awake."; return false; } if (!spawner.m_spawnInPlayerBase && (Object)(object)EffectArea.IsPointInsideArea(((Component)spawner).transform.position, (Type)4, 0f) != (Object)null) { refusal = "Something built here keeps it away."; return false; } if (Blocked(spawner)) { refusal = "Nothing here answers the horn."; return false; } return true; } private static ZDO ZdoOf(CreatureSpawner spawner) { if ((Object)(object)spawner == (Object)null) { return null; } ZNetView component = ((Component)spawner).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return null; } return component.GetZDO(); } private static List Observable(List bosses, List all) { List list = new List(); foreach (CreatureSpawner boss in bosses) { if (!list.Contains(boss)) { list.Add(boss); } foreach (CreatureSpawner item in Dungeons.Groupmates(boss, all)) { if (!list.Contains(item)) { list.Add(item); } } } List list2 = new List(); foreach (CreatureSpawner item2 in list) { ZDO val = ZdoOf(item2); if (val != null) { list2.Add(val); } } return list2; } private static bool Blocked(CreatureSpawner spawner) { List globalKeys = ZoneSystem.instance.GetGlobalKeys(); if (!string.IsNullOrEmpty(spawner.m_blockingGlobalKey) && globalKeys.Contains(spawner.m_blockingGlobalKey)) { return true; } if (!string.IsNullOrEmpty(spawner.m_requiredGlobalKey)) { return !globalKeys.Contains(spawner.m_requiredGlobalKey); } return false; } internal static void Succeeded(ZDO generator, Player player) { if (generator != null) { generator.Set(SoundedKey, (long)ZNet.instance.GetTimeSeconds()); } LurPlugin.Log.LogInfo((object)"Something woke - the horn is spent."); if (!((Object)(object)player == (Object)null)) { Consume(player); ((Character)player).Message((MessageType)2, "Something wakes.", 0, (Sprite)null); } } internal static void TimedOut(List cleared, Player player) { //IL_0020: 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) foreach (Cleared item in cleared) { if (item.Zdo != null) { item.Zdo.SetConnection(item.Type, item.Target); } } LurPlugin.Log.LogInfo((object)("Nothing stirred - put " + cleared.Count + " spawner(s) back as they were. The horn was not spent.")); if (!((Object)(object)player == (Object)null)) { ((Character)player).Message((MessageType)2, "Nothing stirred.", 0, (Sprite)null); } } private static void Consume(Player player) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { ItemData item = inventory.GetItem("Lur", -1, true); if (item != null) { inventory.RemoveOneItem(item); } } } private static void Effect(Vector3 at) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) string value = LurConfig.SoundEffectPrefab.Value; if (!string.IsNullOrEmpty(value)) { GameObject prefab = ZNetScene.instance.GetPrefab(value); if ((Object)(object)prefab == (Object)null) { LurPlugin.LogOnce("Sound effect prefab '" + value + "' does not exist - the horn is silent. Anything else about it is unaffected."); } else { Object.Instantiate(prefab, at, Quaternion.identity); } } } private static void Refuse(Player player, string message) { if (!((Object)(object)player == (Object)null)) { ((Character)player).Message((MessageType)2, message, 0, (Sprite)null); } } } internal class Watch : MonoBehaviour { private List _cleared; private ZDO _generator; private Player _player; private int _wanted; internal List Observe; internal void Begin(List cleared, ZDO generator, Player player, int wantedPrefabHash) { _cleared = cleared; _generator = generator; _player = player; _wanted = wantedPrefabHash; ((MonoBehaviour)this).StartCoroutine(Run()); } private IEnumerator Run() { float deadline = Time.time + Mathf.Max(1f, LurConfig.WatchSeconds.Value); while (Time.time < deadline) { foreach (ZDO item in (Observe != null && Observe.Count > 0) ? Observe : Cleared()) { if (item != null && (int)item.GetConnectionType() == 3) { ZDOID connectionZDOID = item.GetConnectionZDOID((ConnectionType)3); if (!((ZDOID)(ref connectionZDOID)).IsNone() && IsWanted(item, connectionZDOID)) { Sounding.Succeeded(_generator, _player); Done(); yield break; } } } yield return (object)new WaitForSeconds(0.5f); } Sounding.TimedOut(_cleared, _player); Done(); } private List Cleared() { List list = new List(); foreach (Sounding.Cleared item in _cleared) { if (item.Zdo != null) { list.Add(item.Zdo); } } return list; } private bool IsWanted(ZDO zdo, ZDOID target) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (_wanted == 0) { return true; } ZDO zDO = ZDOMan.instance.GetZDO(target); if (zDO == null) { return true; } if (zDO.GetPrefab() == _wanted) { return true; } ManualLogSource log = LurPlugin.Log; string[] obj = new string[7] { "Something spawned, but not what the horn was for - expected prefab hash ", _wanted.ToString(), ", got ", zDO.GetPrefab().ToString(), " at ", null, null }; Vector3 position = zDO.GetPosition(); obj[5] = ((Vector3)(ref position)).ToString("F1"); obj[6] = ". The horn was not spent. This means the boss spawner was mis-identified, so turn on Diagnose and report the census."; log.LogError((object)string.Concat(obj)); if ((Object)(object)_player != (Object)null) { ((Character)_player).Message((MessageType)2, "Something else stirs.", 0, (Sprite)null); } return false; } private void Done() { _cleared = null; _generator = null; _player = null; Object.Destroy((Object)(object)this); } } internal static class Store { private const string Merchant = "Hildir"; private static TradeItem _row; private static ItemDrop _drop; private static TradeItem Row() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown if (_row != null && (Object)(object)_drop != (Object)null) { return _row; } if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab("Lur"); if ((Object)(object)prefab == (Object)null) { return null; } ItemDrop component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { return null; } _drop = component; _row = new TradeItem { m_prefab = component, m_stack = Mathf.Max(1, LurConfig.Stack.Value), m_price = Mathf.Max(0, LurConfig.Price.Value), m_requiredGlobalKey = (LurConfig.SoldAfterKey.Value ?? "") }; LurPlugin.Log.LogInfo((object)("Hildir will stock Lur at " + _row.m_price + " coins.")); return _row; } internal static void Offer(Trader trader, List items) { if (!LurConfig.Enabled.Value || (Object)(object)trader == (Object)null || items == null || Utils.GetPrefabName(((Component)trader).gameObject) != "Hildir") { return; } TradeItem val = Row(); if (val != null) { val.m_price = Mathf.Max(0, LurConfig.Price.Value); val.m_stack = Mathf.Max(1, LurConfig.Stack.Value); val.m_requiredGlobalKey = LurConfig.SoldAfterKey.Value ?? ""; if ((string.IsNullOrEmpty(val.m_requiredGlobalKey) || ZoneSystem.instance.GetGlobalKey(val.m_requiredGlobalKey)) && !items.Contains(val)) { items.Add(val); } } } } }