Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SeneaL API v1.0.1
patchers/SeneaL-API/SeneaL-API-Early.dll
Decompiled 5 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mono.Cecil; [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: AssemblyVersion("0.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 SeneaL.API.Early { public static class EarlyPatcher { public static IEnumerable<string> TargetDLLs => new string[0]; public static void Patch(AssemblyDefinition assembly) { } public static void Initialize() { StubBridge.Start(); } } public sealed class StubCall { public string Owner; public string Method; public object[] Args; public Type[] Types; } public static class StubBridge { public const string RealPlugin = "AzuEPI.AzuExtendedPlayerInventoryPlugin"; public const string DefaultIgnore = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot"; public const string OldIgnore = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Backpacks,RustyBags,Hunter_Legacy,Magic_Supremacy,MagicPlugin,Wizardry"; public const string OldIgnore2 = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Magic_Supremacy"; private static readonly List<StubCall> Calls = new List<StubCall>(8); private static readonly HashSet<MethodBase> Done = new HashSet<MethodBase>(); private static readonly Dictionary<string, Assembly> Origins = new Dictionary<string, Assembly>(8, StringComparer.OrdinalIgnoreCase); private static readonly string[] Names = new string[3] { "IsLoaded", "AddSlot", "RemoveSlot" }; private static ManualLogSource _log; private static Harmony _harmony; private static int _files = -1; private static string[] _ignore; private static bool _off; public static Action Wake; public static int Patched => Done.Count; public static void Start() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if (_harmony != null) { return; } _log = Logger.CreateLogSource("SeneaL API Early"); ReadConfig(); if (_off) { _log.LogInfo((object)"embedded AzuEPI API stubs are left alone (Bridge / AzuEPIStubs = false)"); return; } _harmony = new Harmony("seneaL.valheim.api.early"); AppDomain.CurrentDomain.AssemblyLoad += delegate(object s, AssemblyLoadEventArgs e) { Scan(e.LoadedAssembly); }; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int num = 0; num < assemblies.Length; num++) { Scan(assemblies[num]); } } private static void ReadConfig() { string text = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot"; try { string path = Path.Combine(Paths.ConfigPath, "seneaL.valheim.api.cfg"); if (File.Exists(path)) { string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.StartsWith("AzuEPIStubIgnore", StringComparison.OrdinalIgnoreCase) && text2.IndexOf('=') > 0) { text = text2.Substring(text2.IndexOf('=') + 1).Trim(); } else if (text2.StartsWith("AzuEPIStubs", StringComparison.OrdinalIgnoreCase) && text2.IndexOf('=') > 0) { _off = text2.Substring(text2.IndexOf('=') + 1).Trim().Equals("false", StringComparison.OrdinalIgnoreCase); } } } } catch (Exception ex) { _log.LogWarning((object)("config: " + ex.Message)); } if (text == "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Backpacks,RustyBags,Hunter_Legacy,Magic_Supremacy,MagicPlugin,Wizardry" || text == "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Magic_Supremacy") { text = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot"; } _ignore = (from n in text.Split(new char[1] { ',' }) select n.Trim().ToLowerInvariant() into n where n.Length > 0 select n).ToArray(); } public static StubCall[] Take() { lock (Calls) { StubCall[] result = Calls.ToArray(); Calls.Clear(); return result; } } public static bool Real(Assembly a) { try { return a.GetType("AzuEPI.AzuExtendedPlayerInventoryPlugin", throwOnError: false) != null; } catch { return false; } } public static void Scan(Assembly a) { try { if (a == null || a.IsDynamic) { return; } if (Real(a)) { WatchReal(a); return; } string name = a.GetName().Name; if (Array.IndexOf(_ignore, name.ToLowerInvariant()) >= 0) { if (a.GetType("AzuEPI.API", throwOnError: false) != null) { _log.LogInfo((object)("the AzuEPI API stub in " + name + " is left alone (Bridge / AzuEPIStubIgnore)")); } } else { Patch(a.GetType("AzuEPI.API", throwOnError: false), a); Patch(a.GetType("AzuExtendedPlayerInventory.API", throwOnError: false), a); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("scan " + a?.GetName().Name + ": " + ex.GetType().Name + " " + ex.Message)); } } } public static Assembly OriginOf(string slot) { lock (Origins) { Assembly value; return (!string.IsNullOrEmpty(slot) && Origins.TryGetValue(slot, out value)) ? value : null; } } internal static void Note(string slot, Assembly caller, bool remove) { if (string.IsNullOrEmpty(slot)) { return; } lock (Origins) { if (remove) { Origins.Remove(slot); } else if (caller != null) { Origins[slot] = caller; } } } private static void WatchReal(Assembly a) { WatchType(a.GetType("AzuEPI.API", throwOnError: false)); WatchType(a.GetType("AzuExtendedPlayerInventory.API", throwOnError: false)); } private static void WatchType(Type t) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown if (t == null) { return; } MethodInfo[] methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if ((!(methodInfo.Name != "AddSlot") || !(methodInfo.Name != "RemoveSlot")) && Done.Add(methodInfo)) { try { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Hooks).GetMethod("Real", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { _log.LogWarning((object)("watch " + methodInfo.Name + ": " + ex.GetType().Name + " " + ex.Message)); } } } } private static void Patch(Type t, Assembly a) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown if (t == null) { return; } MethodInfo[] methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (Array.IndexOf(Names, methodInfo.Name) < 0 || methodInfo.ReturnType != typeof(bool) || !Done.Add(methodInfo)) { continue; } try { MethodInfo method = typeof(Hooks).GetMethod((methodInfo.Name == "IsLoaded") ? "IsLoaded" : "Call", BindingFlags.Static | BindingFlags.NonPublic); _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _log.LogInfo((object)("stub " + t.FullName + "." + methodInfo.Name + "(" + string.Join(", ", (from p in methodInfo.GetParameters() select p.ParameterType.Name).ToArray()) + ") in " + a.GetName().Name + " is routed to SeneaL API")); } catch (Exception ex) { _log.LogWarning((object)("patch " + methodInfo.Name + ": " + ex.GetType().Name + " " + ex.Message)); } } } public static bool Answer() { if (_files < 0) { _files = 0; try { string[] files = Directory.GetFiles(Paths.PluginPath, "*.dll", SearchOption.AllDirectories); for (int i = 0; i < files.Length; i++) { string fileName = Path.GetFileName(files[i]); if (string.Equals(fileName, "AzuExtendedPlayerInventory.dll", StringComparison.OrdinalIgnoreCase)) { _files |= 1; } else if (string.Equals(fileName, "SeneaL-UI.dll", StringComparison.OrdinalIgnoreCase)) { _files |= 2; } } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("plugin scan: " + ex.Message)); } } } return _files != 0; } internal static void Enqueue(MethodBase m, object[] args) { lock (Calls) { Calls.Add(new StubCall { Owner = m.DeclaringType.Assembly.GetName().Name, Method = m.Name, Args = args, Types = (from p in m.GetParameters() select p.ParameterType).ToArray() }); } Wake?.Invoke(); } } internal static class Hooks { private static void Real(MethodBase __originalMethod, object[] __args) { try { string text = ((__args != null && __args.Length != 0) ? (__args[0] as string) : null); if (!string.IsNullOrEmpty(text)) { StubBridge.Note(text, Caller(__originalMethod.DeclaringType.Assembly), __originalMethod.Name == "RemoveSlot"); } } catch { } } private static Assembly Caller(Assembly real) { StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: false); for (int i = 0; i < stackTrace.FrameCount; i++) { Assembly assembly = stackTrace.GetFrame(i).GetMethod()?.DeclaringType?.Assembly; if (assembly == null || assembly.IsDynamic || assembly == real || assembly == typeof(Hooks).Assembly) { continue; } string name = assembly.GetName().Name; switch (name) { case "mscorlib": case "netstandard": case "BepInEx": continue; } if (!name.StartsWith("System", StringComparison.Ordinal) && !name.StartsWith("0Harmony", StringComparison.Ordinal) && !name.StartsWith("MonoMod", StringComparison.Ordinal) && !name.StartsWith("Unity", StringComparison.Ordinal) && !name.StartsWith("DMD", StringComparison.Ordinal)) { if (!(name == "SeneaL-API") && !name.StartsWith("assembly_", StringComparison.Ordinal)) { return assembly; } return null; } } return null; } private static bool IsLoaded(ref bool __result) { __result = StubBridge.Answer(); return false; } private static bool Call(MethodBase __originalMethod, object[] __args, ref bool __result) { StubBridge.Enqueue(__originalMethod, __args); __result = true; return false; } } }
plugins/SeneaL-API/SeneaL-API.dll
Decompiled 5 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using SeneaL.API.Early; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SeneaL-API")] [assembly: AssemblyProduct("SeneaL-API")] [assembly: AssemblyCompany("seneaL")] [assembly: AssemblyCopyright("seneaL")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [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 SeneaL.API { public static class Api { public const string Guid = "seneaL.valheim.api"; public const string Name = "SeneaL API"; public const int Contract = 2; public static readonly string[] Features = new string[16] { "notices", "dialogs", "hud", "slots", "slots.linked", "azuepi", "hud.placed", "slots.use", "slots.time", "slots.place", "slots.icon", "theme", "dialogs.choice", "theme.icons", "settings", "slots.handlers" }; public static string Version => "1.0.1"; public static bool Has(string feature) { return Array.IndexOf(Features, feature) >= 0; } } internal static class AzuBridge { private sealed class Entry { public SlotDef Def; public Func<Player, ItemData> Get; public Func<ItemData, bool> Valid; } public const string Guid = "Azumatt.AzuExtendedPlayerInventory"; private static readonly Dictionary<string, Entry> Known = new Dictionary<string, Entry>(8); private static readonly List<string> Gone = new List<string>(4); private static readonly HashSet<string> Seen = new HashSet<string>(); private static Type _bound; private static MethodInfo _slots; private static MethodInfo _desc; private static bool _warned; public static Func<Assembly, string> NameOf; public static int Count => Known.Count; public static bool Bound => _bound != null; public static bool Bind(Type api) { if (api == null) { if (_bound != null) { Clear(); } return false; } if ((object)api == _bound) { return true; } Clear(); MethodInfo method = api.GetMethod("GetSlots", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); MethodInfo method2 = api.GetMethod("TryGetSlotDescriptor", BindingFlags.Static | BindingFlags.Public); if (method == null || method2 == null || method2.GetParameters().Length != 2) { if (!_warned) { _warned = true; Plugin.Warn("AzuEPI is installed, but its slot API has another shape than this bridge knows; its slots are not shown."); } return false; } _slots = method; _desc = method2; _bound = api; return true; } private static string OriginOf(Delegate fn, Type api) { Assembly assembly = fn?.Method.DeclaringType?.Assembly; if (assembly == null || (api != null && assembly == api.Assembly)) { return null; } if (NameOf == null) { return assembly.GetName().Name; } return NameOf(assembly); } public static void Clear() { CustomSlots.RemoveAll("Azumatt.AzuExtendedPlayerInventory"); Known.Clear(); _bound = null; _slots = (_desc = null); } public static void Sync() { if (_bound == null) { return; } object obj; try { obj = _slots.Invoke(null, null); } catch (Exception e) { Plugin.Fail("AzuEPI GetSlots", e); return; } if (obj == null) { return; } Type type = obj.GetType(); string[] array = (string[])type.GetProperty("SlotNames")?.GetValue(obj, null); string[] array2 = (string[])type.GetProperty("OriginalSlotNames")?.GetValue(obj, null); Array array3 = (Array)(type.GetProperty("GetItemFuncs")?.GetValue(obj, null)); Array array4 = (Array)(type.GetProperty("IsValidFuncs")?.GetValue(obj, null)); if (array == null || array2 == null) { return; } Seen.Clear(); for (int i = 0; i < array.Length; i++) { object[] array5 = new object[2] { i, null }; if (!(bool)_desc.Invoke(null, array5) || array5[1] == null) { continue; } object obj2 = array5[1]; Type type2 = obj2.GetType(); if (!(bool)type2.GetProperty("IsCustom").GetValue(obj2, null) || (bool)type2.GetProperty("IsQuickSlot").GetValue(obj2, null)) { continue; } string text = (string.IsNullOrEmpty(array2[i]) ? array[i] : array2[i]); if (string.IsNullOrEmpty(text)) { continue; } Seen.Add(text); Func<Player, ItemData> func = ((array3 != null && i < array3.Length) ? (array3.GetValue(i) as Func<Player, ItemData>) : null); Func<ItemData, bool> valid = ((array4 != null && i < array4.Length) ? (array4.GetValue(i) as Func<ItemData, bool>) : null); if (!Known.TryGetValue(text, out var value)) { Entry entry; value = (entry = new Entry()); value.Def = new SlotDef(text) { Title = array[i], Origin = (SlotOrigins.Get(text) ?? SlotOrigins.Get(array[i]) ?? OriginOf(func, _bound)), Mode = SlotMode.Linked, Order = 1000 + i, Source = (Player p) => entry.Get?.Invoke(p), Accepts = (ItemData it) => entry.Valid == null || entry.Valid(it) }; value.Get = func; value.Valid = valid; Known[text] = value; CustomSlots.Add("Azumatt.AzuExtendedPlayerInventory", value.Def); } else { value.Get = func; value.Valid = valid; if (string.IsNullOrEmpty(value.Def.Origin)) { value.Def.Origin = SlotOrigins.Get(text) ?? SlotOrigins.Get(array[i]) ?? OriginOf(func, _bound); } if (value.Def.Title != array[i]) { value.Def.Title = array[i]; } } } Gone.Clear(); foreach (string key in Known.Keys) { if (!Seen.Contains(key)) { Gone.Add(key); } } for (int num = 0; num < Gone.Count; num++) { Known.Remove(Gone[num]); CustomSlots.Remove("Azumatt.AzuExtendedPlayerInventory", Gone[num]); } } } internal static class SlotOrigins { private static readonly Dictionary<string, string> Names = new Dictionary<string, string>(8, StringComparer.OrdinalIgnoreCase); public static Func<string, string> Fallback; public static int Count => Names.Count; public static void Record(string slot, string mod) { if (!string.IsNullOrEmpty(slot) && !string.IsNullOrEmpty(mod)) { Names[slot] = mod; } } public static void Forget(string slot) { if (!string.IsNullOrEmpty(slot)) { Names.Remove(slot); } } public static string Get(string slot) { if (!string.IsNullOrEmpty(slot)) { if (!Names.TryGetValue(slot, out var value)) { return Fallback?.Invoke(slot); } return value; } return null; } public static void Clear() { Names.Clear(); } public static Assembly Pick(IList<Assembly> frames, Assembly own, Func<Assembly, bool> skip) { for (int i = 0; i < frames.Count; i++) { Assembly assembly = frames[i]; if (!(assembly == null) && (skip == null || !skip(assembly))) { if (!(assembly == own)) { return assembly; } return null; } } return null; } } internal static class StubHost { private sealed class Emu { public SlotDef Def; public readonly List<Func<Player, ItemData>> Gets = new List<Func<Player, ItemData>>(2); public readonly List<Func<ItemData, bool>> Valids = new List<Func<ItemData, bool>>(2); } public const string Owner = "AzuEPI.emulated"; private static readonly Dictionary<string, Emu> Emus = new Dictionary<string, Emu>(8); private static bool _on; public static void Start() { if (!_on && AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "SeneaL-API-Early")) { _on = true; Hook(); } } private static void Hook() { StubBridge.Wake = Drain; Plugin.Log.LogInfo((object)("AzuEPI API stubs embedded in other mods: " + StubBridge.Patched + " methods routed here")); Drain(); } private static void Drain() { StubCall[] array = StubBridge.Take(); foreach (StubCall val in array) { try { Handle(val); } catch (Exception e) { Plugin.Fail("AzuEPI stub " + val.Owner + "." + val.Method, e); } } } private static void Handle(StubCall c) { Plugin.Trace("AzuEPI stub " + c.Owner + "." + c.Method + " " + ((c.Args.Length != 0) ? c.Args[0] : "")); if (c.Args.Length != 0 && c.Args[0] is string slot) { if (c.Method == "AddSlot") { SlotOrigins.Record(slot, PluginName(c.Owner)); } else if (c.Method == "RemoveSlot") { SlotOrigins.Forget(slot); } } if (Chainloader.PluginInfos.TryGetValue("Azumatt.AzuExtendedPlayerInventory", out var value) && Object.op_Implicit((Object)(object)value.Instance)) { MethodInfo methodInfo = ((object)value.Instance).GetType().Assembly.GetType("AzuEPI.API")?.GetMethod(c.Method, c.Types); if (methodInfo != null) { methodInfo.Invoke(null, c.Args); return; } } if (!CustomSlots.AzuManaged) { Emulate(c); } } private static void Emulate(StubCall c) { string text = ((c.Args.Length != 0) ? (c.Args[0] as string) : null); if (string.IsNullOrWhiteSpace(text)) { return; } if (c.Method == "RemoveSlot") { Emus.Remove(text); CustomSlots.Remove("AzuEPI.emulated", text); return; } Func<Player, ItemData> func = null; Func<ItemData, bool> valid = null; if (c.Args.Length == 4 && c.Args[1] is Func<Player, ItemData> func2) { func = func2; valid = c.Args[2] as Func<ItemData, bool>; } else { if (c.Args.Length == 3) { object obj = c.Args[1]; string prefab = obj as string; if (prefab != null) { valid = (ItemData it) => it != null && Object.op_Implicit((Object)(object)it.m_dropPrefab) && ((Object)it.m_dropPrefab).name == prefab; goto IL_0170; } } if (c.Args.Length == 3 && c.Args[1] is IEnumerable<string> source) { HashSet<string> set = new HashSet<string>(source.Where((string n) => !string.IsNullOrWhiteSpace(n))); valid = (ItemData it) => it != null && Object.op_Implicit((Object)(object)it.m_dropPrefab) && set.Contains(((Object)it.m_dropPrefab).name); } else if (c.Args.Length == 4 && c.Args[1] is Func<ItemData, bool> func3) { valid = func3; } } goto IL_0170; IL_0170: if (func == null && valid == null) { return; } if (!Emus.TryGetValue(text, out var value)) { Emu emu; value = (emu = new Emu()); value.Def = new SlotDef(text) { Title = text, Origin = PluginName(c.Owner), Mode = SlotMode.Linked, Order = 2000 + Emus.Count, Source = (Player p) => Find(emu, p), Accepts = (ItemData it) => emu.Valids.Any((Func<ItemData, bool> f) => f(it)) }; Emus[text] = value; } if (func != null) { value.Gets.Add(func); } if (valid != null) { value.Valids.Add(valid); if (func == null) { value.Gets.Add(delegate(Player p) { if (p == null) { return (ItemData)null; } Inventory inventory = ((Humanoid)p).GetInventory(); return (inventory == null) ? null : inventory.GetEquippedItems()?.FirstOrDefault(valid); }); } } CustomSlots.Add("AzuEPI.emulated", value.Def); } private static string PluginName(string asm) { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (Object.op_Implicit((Object)(object)value.Instance) && ((object)value.Instance).GetType().Assembly.GetName().Name == asm) { return value.Metadata.Name; } } return asm; } private static ItemData Find(Emu e, Player p) { for (int i = 0; i < e.Gets.Count; i++) { ItemData val = e.Gets[i](p); if (val != null) { return val; } } return null; } } internal static class BuildInfo { public const string Channel = "release"; public const string Version = "1.0.1"; public const int Number = 0; public const string Time = "2026-10-18 12:14 UTC"; public const string Id = "0eead253"; } internal sealed class Slot<T> where T : class { private sealed class Entry { public string Owner; public T Provider; public int Priority; public int Serial; } private readonly List<Entry> _items = new List<Entry>(2); private int _serial; public T Current { get; private set; } public string Owner { get; private set; } public int Count => _items.Count; public event Action<T> Changed; public void Set(string owner, T provider, int priority) { if (string.IsNullOrEmpty(owner)) { throw new ArgumentException("owner is required", "owner"); } if (provider == null) { throw new ArgumentNullException("provider"); } Drop(owner); _items.Add(new Entry { Owner = owner, Provider = provider, Priority = priority, Serial = ++_serial }); Pick(); } public T Get(string owner) { for (int i = 0; i < _items.Count; i++) { if (_items[i].Owner == owner) { return _items[i].Provider; } } return null; } public bool Remove(string owner) { if (!Drop(owner)) { return false; } Pick(); return true; } private bool Drop(string owner) { for (int i = 0; i < _items.Count; i++) { if (!(_items[i].Owner != owner)) { _items.RemoveAt(i); return true; } } return false; } private void Pick() { Entry entry = null; for (int i = 0; i < _items.Count; i++) { Entry entry2 = _items[i]; if (entry == null || entry2.Priority > entry.Priority || (entry2.Priority == entry.Priority && entry2.Serial > entry.Serial)) { entry = entry2; } } T val = ((entry != null) ? entry.Provider : null); if (val != Current) { Current = val; Owner = entry?.Owner; this.Changed?.Invoke(val); } } } public enum DialogKind { Confirm, Message, Input, Choice } public abstract class Dialog { internal int Depth; internal string Owner; internal int Frame; internal Action Fallback; public abstract DialogKind Kind { get; } public string Title { get; set; } public string Text { get; set; } public bool Centered { get; set; } public bool IsOpen { get; internal set; } public bool Vanilla { get; internal set; } public void Close() { Dialogs.Close(this); } internal void Answer(Action callback) { if (IsOpen) { if (!Vanilla) { Dialogs.Close(this); } try { callback?.Invoke(); } catch (Exception e) { Plugin.Fail("dialog callback", e); } Dialogs.Finish(this); } } } public sealed class ConfirmDialog : Dialog { private readonly Action _yes; private readonly Action _no; public override DialogKind Kind => DialogKind.Confirm; public string YesText { get; set; } public string NoText { get; set; } public ConfirmDialog(Action yes, Action no) { _yes = yes; _no = no; } public void Yes() { Answer(_yes); } public void No() { Answer(_no); } } public sealed class MessageDialog : Dialog { private readonly Action _ok; public override DialogKind Kind => DialogKind.Message; public string OkText { get; set; } public MessageDialog(Action ok) { _ok = ok; } public void Ok() { Answer(_ok); } } public sealed class InputDialog : Dialog { private readonly Action<string> _submit; private readonly Action _cancel; private readonly Func<string, bool> _validate; public override DialogKind Kind => DialogKind.Input; public string Value { get; set; } public string Placeholder { get; set; } public int CharLimit { get; set; } public InputDialog(Action<string> submit, Action cancel, Func<string, bool> validate) { _submit = submit; _cancel = cancel; _validate = validate; } public bool IsValid(string value) { if (_validate != null) { return _validate(value); } return true; } public void Submit(string value) { if (base.IsOpen && IsValid(value)) { Answer(delegate { _submit?.Invoke(value); }); } } public void Cancel() { Answer(_cancel); } } public sealed class DialogButton { public string Text { get; set; } public bool Primary { get; set; } public Action Click { get; set; } public DialogButton(string text, Action click, bool primary = false) { Text = text; Click = click; Primary = primary; } } public sealed class ChoiceDialog : Dialog { private readonly DialogButton[] _buttons; public override DialogKind Kind => DialogKind.Choice; public IReadOnlyList<DialogButton> Buttons => _buttons; public int CancelIndex { get; set; } public ChoiceDialog(DialogButton[] buttons) { _buttons = buttons ?? new DialogButton[0]; CancelIndex = _buttons.Length - 1; } public void Choose(int index) { if (index >= 0 && index < _buttons.Length) { Answer(_buttons[index].Click); } } public void Cancel() { if (CancelIndex >= 0) { Choose(CancelIndex); } else { Close(); } } } public abstract class DialogProvider { public abstract bool Show(Dialog dialog); public virtual void Hide(Dialog dialog) { } } [HarmonyPatch(typeof(UnifiedPopup), "Push")] internal static class PushPopupPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static bool Pre(PopupBase popup, bool __runOriginal) { if (__runOriginal) { return !Dialogs.InterceptPush(popup); } return true; } } [HarmonyPatch(typeof(UnifiedPopup), "Pop")] internal static class PopPopupPatch { [HarmonyPrefix] private static bool Pre() { return !Dialogs.InterceptPop(); } } [HarmonyPatch(typeof(UnifiedPopup), "IsVisible")] internal static class PopupVisiblePatch { [HarmonyPostfix] private static void Post(ref bool __result) { if (!__result) { __result = Dialogs.IsOpen; } } } [HarmonyPatch(typeof(UnifiedPopup), "WasVisibleThisFrame")] internal static class PopupWasVisiblePatch { [HarmonyPostfix] private static void Post(ref bool __result) { if (!__result) { __result = Dialogs.ClosedThisFrame; } } } [HarmonyPatch(typeof(TextInput), "RequestText")] internal static class RequestTextPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static bool Pre(TextReceiver sign, string topic, int charLimit, bool __runOriginal) { if (__runOriginal) { return !Dialogs.InterceptText(sign, topic, charLimit); } return true; } } [HarmonyPatch(typeof(TextInput), "IsVisible")] internal static class TextVisiblePatch { [HarmonyPostfix] private static void Post(ref bool __result) { if (!__result) { __result = Dialogs.InputOpen; } } } public static class Dialogs { private static readonly Slot<DialogProvider> Providers = new Slot<DialogProvider>(); private static readonly List<Dialog> Opened = new List<Dialog>(2); private static readonly FieldRef<UnifiedPopup, Stack<PopupBase>> Stack = AccessTools.FieldRefAccess<UnifiedPopup, Stack<PopupBase>>("popupStack"); private static readonly FieldInfo Instance = AccessTools.Field(typeof(UnifiedPopup), "instance"); private static bool _passing; private static int _closedFrame = -1; public static bool HasProvider => Providers.Current != null; public static bool BridgeEnabled { get { if (Plugin.BridgeDialogs != null) { return Plugin.BridgeDialogs.Value; } return false; } } public static bool IsOpen => Opened.Count > 0; public static Dialog Top { get { if (Opened.Count <= 0) { return null; } return Opened[Opened.Count - 1]; } } public static bool InputOpen => Opened.Exists(IsInput); public static bool ClosedThisFrame => _closedFrame == Time.frameCount; public static void Register(string owner, DialogProvider provider, int priority = 0) { List<Dialog> orphans = Release(owner); Providers.Set(owner, provider, priority); Replace(orphans); } public static bool Unregister(string owner) { List<Dialog> orphans = Release(owner); bool result = Providers.Remove(owner); Replace(orphans); return result; } public static ConfirmDialog Confirm(string title, string text, Action yes, Action no = null, string yesText = null, string noText = null) { PopupButtonCallback val = default(PopupButtonCallback); PopupButtonCallback val4 = default(PopupButtonCallback); ConfirmDialog obj = new ConfirmDialog(yes, no) { Title = Localize(title), Text = Localize(text), YesText = yesText, NoText = noText, Fallback = delegate { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_002a: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0049: Expected O, but got Unknown string text2 = title; string text3 = text; PopupButtonCallback obj2 = val; if (obj2 == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); yes?.Invoke(); }; PopupButtonCallback val3 = val2; val = val2; obj2 = val3; } PopupButtonCallback obj3 = val4; if (obj3 == null) { PopupButtonCallback val5 = delegate { UnifiedPopup.Pop(); no?.Invoke(); }; PopupButtonCallback val3 = val5; val4 = val5; obj3 = val3; } UnifiedPopup.Push((PopupBase)new YesNoPopup(text2, text3, obj2, obj3, true, true)); } }; Open(obj); return obj; } public static ChoiceDialog Choose(string title, string text, params DialogButton[] buttons) { if (buttons == null || buttons.Length == 0) { throw new ArgumentException("at least one button is required", "buttons"); } DialogButton[] array = buttons; for (int i = 0; i < array.Length; i++) { if (array[i] == null) { throw new ArgumentNullException("buttons"); } } PopupButtonCallback val = default(PopupButtonCallback); PopupButtonCallback val4 = default(PopupButtonCallback); PopupButtonCallback val6 = default(PopupButtonCallback); ChoiceDialog obj = new ChoiceDialog(buttons) { Title = Localize(title), Text = Localize(text), Fallback = delegate { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_006c: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0035: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_008b: Expected O, but got Unknown if (buttons.Length == 1) { string text2 = title; string text3 = text; PopupButtonCallback obj2 = val; if (obj2 == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); buttons[0].Click?.Invoke(); }; PopupButtonCallback val3 = val2; val = val2; obj2 = val3; } UnifiedPopup.Push((PopupBase)new WarningPopup(text2, text3, obj2, true)); } else { string text4 = title; string text5 = text; PopupButtonCallback obj3 = val4; if (obj3 == null) { PopupButtonCallback val5 = delegate { UnifiedPopup.Pop(); buttons[0].Click?.Invoke(); }; PopupButtonCallback val3 = val5; val4 = val5; obj3 = val3; } PopupButtonCallback obj4 = val6; if (obj4 == null) { PopupButtonCallback val7 = delegate { UnifiedPopup.Pop(); buttons[1].Click?.Invoke(); }; PopupButtonCallback val3 = val7; val6 = val7; obj4 = val3; } UnifiedPopup.Push((PopupBase)new YesNoPopup(text4, text5, obj3, obj4, true, true)); } } }; Open(obj); return obj; } public static MessageDialog Message(string title, string text, Action ok = null, string okText = null) { PopupButtonCallback val = default(PopupButtonCallback); MessageDialog obj = new MessageDialog(ok) { Title = Localize(title), Text = Localize(text), OkText = okText, Fallback = delegate { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_002a: Expected O, but got Unknown string text2 = title; string text3 = text; PopupButtonCallback obj2 = val; if (obj2 == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); ok?.Invoke(); }; PopupButtonCallback val3 = val2; val = val2; obj2 = val3; } UnifiedPopup.Push((PopupBase)new WarningPopup(text2, text3, obj2, true)); } }; Open(obj); return obj; } public static InputDialog Input(string title, string value, Action<string> submit, Action cancel = null, int charLimit = 0, Func<string, bool> validate = null, string placeholder = null) { PopupButtonCallback val = default(PopupButtonCallback); ValidateTextEntry val4 = default(ValidateTextEntry); RetrieveTextEntryResult val7 = default(RetrieveTextEntryResult); InputDialog obj = new InputDialog(submit, cancel, validate) { Title = Localize(title), Value = value, CharLimit = charLimit, Placeholder = placeholder, Fallback = delegate { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0030: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_004f: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_006e: Expected O, but got Unknown string text = title; string text2 = value; string text3 = placeholder; PopupButtonCallback obj2 = val; if (obj2 == null) { PopupButtonCallback val2 = delegate { UnifiedPopup.Pop(); cancel?.Invoke(); }; PopupButtonCallback val3 = val2; val = val2; obj2 = val3; } ValidateTextEntry obj3 = val4; if (obj3 == null) { ValidateTextEntry val5 = (string s) => validate == null || validate(s); ValidateTextEntry val6 = val5; val4 = val5; obj3 = val6; } RetrieveTextEntryResult obj4 = val7; if (obj4 == null) { RetrieveTextEntryResult val8 = delegate(string s) { UnifiedPopup.Pop(); submit?.Invoke(s); }; RetrieveTextEntryResult val9 = val8; val7 = val8; obj4 = val9; } UnifiedPopup.Push((PopupBase)new TextEntryPopup(text, text2, text3, obj2, obj3, obj4, true)); } }; Open(obj); return obj; } public static void Close(Dialog dialog) { if (dialog != null && dialog.IsOpen) { dialog.IsOpen = false; Opened.Remove(dialog); _closedFrame = Time.frameCount; Hide(Providers.Current, dialog); } } private static void Hide(DialogProvider p, Dialog dialog) { if (p == null) { return; } try { p.Hide(dialog); } catch (Exception e) { Plugin.Fail("DialogProvider " + dialog.Owner, e); } } internal static void DropStale() { for (int num = Opened.Count - 1; num >= 0; num--) { if (Opened[num].Frame < Time.frameCount) { Dialog dialog = Opened[num]; Close(dialog); Plugin.Trace("dialog dropped on a scene change: " + dialog.Title); } } } internal static void Finish(Dialog d) { if (d.IsOpen && Opened.Count > 0 && Opened[Opened.Count - 1] == d) { Close(d); } } internal static bool InterceptPush(PopupBase popup) { if (_passing || Providers.Current == null || !Plugin.BridgeDialogs.Value) { return false; } Dialog dialog = FromPopup(popup); if (dialog == null) { return false; } dialog.Vanilla = true; dialog.Fallback = delegate { UnifiedPopup.Push(popup); }; return Present(dialog); } internal static bool InterceptText(TextReceiver sign, string topic, int charLimit) { if (_passing || sign == null || Providers.Current == null || !Plugin.BridgeDialogs.Value) { return false; } return Present(new InputDialog((Action<string>)sign.SetText, (Action)null, (Func<string, bool>)null) { Title = Localize(topic), Value = sign.GetText(), CharLimit = charLimit, Vanilla = true, Fallback = delegate { if ((Object)(object)TextInput.instance != (Object)null) { TextInput.instance.RequestText(sign, topic, charLimit); } } }); } internal static bool InterceptPop() { int count = Opened.Count; if (count == 0) { return false; } Dialog dialog = Opened[count - 1]; if (dialog.Depth != Depth()) { return false; } Close(dialog); return true; } private static Dialog FromPopup(PopupBase popup) { YesNoPopup val = (YesNoPopup)(object)((popup is YesNoPopup) ? popup : null); if (val != null) { return new ConfirmDialog((val.yesCallback == null) ? null : new Action(val.yesCallback.Invoke), (val.noCallback == null) ? null : new Action(val.noCallback.Invoke)) { Title = ((FixedPopupBase)val).header, Text = ((FixedPopupBase)val).text }; } WarningPopup val2 = (WarningPopup)(object)((popup is WarningPopup) ? popup : null); if (val2 != null) { PopupButtonCallback okCallback = val2.okCallback; return new MessageDialog((okCallback == null) ? null : new Action(okCallback.Invoke)) { Title = ((FixedPopupBase)val2).header, Text = ((FixedPopupBase)val2).text }; } TextEntryPopup val3 = (TextEntryPopup)(object)((popup is TextEntryPopup) ? popup : null); if (val3 != null) { PopupButtonCallback cancelCallback = val3.cancelCallback; RetrieveTextEntryResult sendResultCallback = val3.sendResultCallback; ValidateTextEntry validationCallback = val3.validationCallback; return new InputDialog((sendResultCallback == null) ? null : new Action<string>(sendResultCallback.Invoke), (cancelCallback == null) ? null : new Action(cancelCallback.Invoke), (validationCallback == null) ? null : new Func<string, bool>(validationCallback.Invoke)) { Title = ((FixedPopupBase)val3).header, Text = ((FixedPopupBase)val3).text, Placeholder = val3.placeholderString }; } return null; } private static bool Present(Dialog d) { DialogProvider current = Providers.Current; if (current == null) { return false; } d.IsOpen = true; d.Depth = Depth(); d.Owner = Providers.Owner; d.Frame = Time.frameCount; Opened.Add(d); bool flag; try { flag = current.Show(d); } catch (Exception e) { Plugin.Fail("DialogProvider " + Providers.Owner, e); flag = false; } if (!flag && d.IsOpen) { d.IsOpen = false; Opened.Remove(d); } Plugin.Trace("dialog " + d.Kind.ToString() + (d.Vanilla ? " (game)" : "") + (flag ? "" : " (declined)") + ": " + d.Title); return flag; } private static void Open(Dialog d) { if (!Present(d)) { Pass(d.Fallback); } } private static List<Dialog> Release(string owner) { List<Dialog> list = null; for (int num = Opened.Count - 1; num >= 0; num--) { Dialog dialog = Opened[num]; if (!(dialog.Owner != owner)) { dialog.IsOpen = false; Opened.RemoveAt(num); Hide(Providers.Get(owner), dialog); (list ?? (list = new List<Dialog>(2))).Insert(0, dialog); } } return list; } private static void Replace(List<Dialog> orphans) { if (orphans == null) { return; } foreach (Dialog orphan in orphans) { if (!Present(orphan)) { Pass(orphan.Fallback); } } } private static void Pass(Action push) { if (push == null) { return; } _passing = true; try { push(); } finally { _passing = false; } } private static bool IsInput(Dialog d) { return d.Kind == DialogKind.Input; } private static string Localize(string s) { Localization instance = Localization.instance; if (instance != null && !string.IsNullOrEmpty(s)) { return instance.Localize(s); } return s; } private static int Depth() { object? value = Instance.GetValue(null); UnifiedPopup val = (UnifiedPopup)((value is UnifiedPopup) ? value : null); if (!((Object)(object)val == (Object)null)) { return Stack.Invoke(val).Count; } return 0; } } public sealed class HudElement { public bool Busy { get; set; } public bool Placed { get; set; } public string Owner { get; } public string Id { get; } public string Key => Owner + "/" + Id; public string Title { get; set; } public RectTransform Rect { get; } public bool Alive => (Object)(object)Rect != (Object)null; internal HudElement(string owner, string id, string title, RectTransform rect) { Owner = owner; Id = id; Title = title; Rect = rect; } } public abstract class HudProvider { public abstract void Added(HudElement element); public virtual void Removed(HudElement element) { } } public static class HudElements { private static readonly Slot<HudProvider> Providers = new Slot<HudProvider>(); private static readonly List<HudElement> Items = new List<HudElement>(8); private static HudProvider _fed; public static bool HasProvider => Providers.Current != null; public static void Register(string owner, HudProvider provider, int priority = 0) { Providers.Set(owner, provider, priority); Feed(); } public static bool Unregister(string owner) { if (!Providers.Remove(owner)) { return false; } Feed(); return true; } public static HudElement Add(string owner, string id, string title, RectTransform rect) { if (string.IsNullOrEmpty(owner)) { throw new ArgumentException("owner is required", "owner"); } if (string.IsNullOrEmpty(id)) { throw new ArgumentException("id is required", "id"); } if ((Object)(object)rect == (Object)null) { throw new ArgumentNullException("rect"); } Prune(); for (int i = 0; i < Items.Count; i++) { if (Items[i].Owner == owner && Items[i].Id == id) { Drop(i); } } HudElement hudElement = new HudElement(owner, id, title, rect); Items.Add(hudElement); Notify(Providers.Current, hudElement); return hudElement; } public static bool Remove(HudElement element) { int num = Items.IndexOf(element); if (num < 0) { return false; } Drop(num); return true; } public static int RemoveAll(string owner) { int num = 0; for (int num2 = Items.Count - 1; num2 >= 0; num2--) { if (!(Items[num2].Owner != owner)) { Drop(num2); num++; } } return num; } public static HudElement Find(string owner, string id) { Prune(); for (int i = 0; i < Items.Count; i++) { if (Items[i].Owner == owner && Items[i].Id == id) { return Items[i]; } } return null; } public static HudElement[] Snapshot() { Prune(); return Items.ToArray(); } private static void Drop(int i) { HudElement element = Items[i]; Items.RemoveAt(i); HudProvider current = Providers.Current; if (current == null) { return; } try { current.Removed(element); } catch (Exception e) { Plugin.Fail("HudProvider " + Providers.Owner, e); } } private static void Prune() { for (int num = Items.Count - 1; num >= 0; num--) { if (!Items[num].Alive) { Drop(num); } } } private static void Feed() { HudProvider current = Providers.Current; if (current == _fed) { return; } _fed = current; if (current != null) { Prune(); for (int i = 0; i < Items.Count; i++) { Notify(current, Items[i]); } } } private static void Notify(HudProvider p, HudElement e) { if (p == null) { return; } try { p.Added(e); } catch (Exception e2) { Plugin.Fail("HudProvider " + Providers.Owner, e2); } } } public enum NoticeKind { TopLeft, Center, Unlock, Biome } public sealed class Notice { public bool Log { get; set; } = true; public NoticeKind Kind { get; set; } public string Raw { get; set; } public string Text { get; set; } public string Title { get; set; } public int Amount { get; set; } public Sprite Icon { get; set; } public bool Stinger { get; set; } public bool ShowWhenHidden { get; set; } public bool Vanilla { get; internal set; } } public abstract class NoticeProvider { public abstract bool Show(Notice notice); } [HarmonyPatch(typeof(MessageHud), "ShowMessage")] internal static class ShowMessagePatch { [HarmonyPrefix] private static bool Pre(MessageType type, string text, int amount, Sprite icon, bool showDespiteHiddenHUD, bool log) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if ((int)type == 1) { return !Notices.Intercept(NoticeKind.TopLeft, text, null, amount, icon, showDespiteHiddenHUD, log, stinger: false); } if ((int)type == 2) { return !Notices.Intercept(NoticeKind.Center, text, null, amount, icon, showDespiteHiddenHUD, log, stinger: false); } return true; } } [HarmonyPatch(typeof(MessageHud), "QueueUnlockMsg")] internal static class QueueUnlockPatch { [HarmonyPrefix] private static bool Pre(Sprite icon, string topic, string description) { return !Notices.Intercept(NoticeKind.Unlock, description, topic, 0, icon, force: true, log: true, stinger: false); } } [HarmonyPatch(typeof(MessageHud), "ShowBiomeFoundMsg")] internal static class BiomeFoundPatch { [HarmonyPrefix] private static bool Pre(string text, bool playStinger) { return !Notices.Intercept(NoticeKind.Biome, text, null, 0, null, force: true, log: false, playStinger); } } public static class Notices { private static readonly Slot<NoticeProvider> Providers = new Slot<NoticeProvider>(); private static bool _passing; private static readonly Action<MessageHud, string> Add = (Action<MessageHud, string>)Delegate.CreateDelegate(typeof(Action<MessageHud, string>), AccessTools.Method(typeof(MessageHud), "AddLog", (Type[])null, (Type[])null)); public static bool HasProvider => Providers.Current != null; public static bool BridgeEnabled { get { if (Plugin.BridgeNotices != null) { return Plugin.BridgeNotices.Value; } return false; } } public static event Action<Notice> Shown; public static void Register(string owner, NoticeProvider provider, int priority = 0) { Providers.Set(owner, provider, priority); } public static bool Unregister(string owner) { return Providers.Remove(owner); } public static void Show(string text, NoticeKind kind = NoticeKind.TopLeft, int amount = 0, Sprite icon = null, bool log = true) { if (kind == NoticeKind.Unlock || kind == NoticeKind.Biome) { throw new ArgumentException("use Unlock or Biome", "kind"); } Send(Make(kind, text, null, amount, icon, log, vanilla: false)); } public static void Unlock(Sprite icon, string title, string text) { Notice notice = Make(NoticeKind.Unlock, text, title, 0, icon, log: true, vanilla: false); notice.ShowWhenHidden = true; Send(notice); } public static void Biome(string text, bool stinger = true) { Notice notice = Make(NoticeKind.Biome, text, null, 0, null, log: false, vanilla: false); notice.Stinger = stinger; notice.ShowWhenHidden = true; Send(notice); } public static void Send(Notice notice) { if (notice == null) { throw new ArgumentNullException("notice"); } if (notice.Raw == null) { notice.Raw = notice.Text; } if (notice.Text == null) { notice.Text = Localize(notice.Raw); } if (!Dispatch(notice)) { Pass(notice); } } internal static bool Intercept(NoticeKind kind, string raw, string title, int amount, Sprite icon, bool force, bool log, bool stinger) { if (_passing || (Providers.Current == null && Notices.Shown == null) || !Plugin.BridgeNotices.Value) { return false; } Notice notice = Make(kind, raw, title, amount, icon, log, vanilla: true); notice.ShowWhenHidden = force; notice.Stinger = stinger; return Dispatch(notice); } private static Notice Make(NoticeKind kind, string raw, string title, int amount, Sprite icon, bool log, bool vanilla) { return new Notice { Kind = kind, Raw = raw, Text = Localize(raw), Title = ((title == null) ? null : Localize(title)), Amount = amount, Icon = icon, Log = log, Vanilla = vanilla }; } private static string Localize(string s) { Localization instance = Localization.instance; if (instance != null && !string.IsNullOrEmpty(s)) { return instance.Localize(s); } return s; } private static bool Dispatch(Notice n) { if (Hud.IsUserHidden() && !n.ShowWhenHidden) { return true; } Plugin.Trace("notice " + n.Kind.ToString() + (n.Vanilla ? " (game)" : "") + ": " + n.Text); Action<Notice> shown = Notices.Shown; if (shown != null) { Delegate[] invocationList = shown.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action<Notice>)obj)(n); } catch (Exception e) { Plugin.Fail("Notices.Shown", e); } } } NoticeProvider current = Providers.Current; if (current == null) { return false; } bool flag; try { flag = current.Show(n); } catch (Exception e2) { Plugin.Fail("NoticeProvider " + Providers.Owner, e2); return false; } if (flag && n.Log) { Journal(n); } return flag; } private static void Journal(Notice n) { MessageHud instance = MessageHud.instance; if (!((Object)(object)instance == (Object)null) && n.Kind != NoticeKind.Biome) { Add(instance, (n.Kind == NoticeKind.Unlock) ? (n.Title + ": " + n.Text) : n.Text); } } private static void Pass(Notice n) { MessageHud instance = MessageHud.instance; if ((Object)(object)instance == (Object)null) { return; } _passing = true; try { switch (n.Kind) { case NoticeKind.TopLeft: instance.ShowMessage((MessageType)1, n.Raw, n.Amount, n.Icon, n.ShowWhenHidden, n.Log); break; case NoticeKind.Center: instance.ShowMessage((MessageType)2, n.Raw, n.Amount, n.Icon, n.ShowWhenHidden, n.Log); break; case NoticeKind.Unlock: instance.QueueUnlockMsg(n.Icon, n.Title, n.Raw); break; case NoticeKind.Biome: instance.ShowBiomeFoundMsg(n.Raw, n.Stinger); break; } } finally { _passing = false; } } } [BepInPlugin("seneaL.valheim.api", "SeneaL API", "1.0.1")] public class Plugin : BaseUnityPlugin { public const string Version = "1.0.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> BridgeNotices; internal static ConfigEntry<bool> BridgeDialogs; internal static ConfigEntry<bool> BridgeAzu; internal static ConfigEntry<bool> BridgeStubs; internal static ConfigEntry<string> StubIgnore; internal static ConfigEntry<bool> Verbose; private float _azuAt; private float _pollAt; private Harmony _harmony; private void Awake() { //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BridgeNotices = ((BaseUnityPlugin)this).Config.Bind<bool>("Bridge", "Notices", true, "Route the game's own messages (top-left, center, unlock and biome banners) to the notice provider, if one is registered."); BridgeDialogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Bridge", "Dialogs", true, "Route the game's own confirm, warning and text input windows to the dialog provider, if one is registered."); BridgeAzu = ((BaseUnityPlugin)this).Config.Bind<bool>("Bridge", "AzuEPI", true, "Show the equipment slots that other mods add through Azumatt's AzuEPI in the UI mod as slots of this API, if AzuEPI is installed. AzuEPI keeps running them; the UI only shows and moves the items."); BridgeStubs = ((BaseUnityPlugin)this).Config.Bind<bool>("Bridge", "AzuEPIStubs", true, "Route the AzuEPI API stubs that other mods carry inside their own dll (their methods do nothing) to this API, so such a mod's AddSlot reaches the UI mod (through AzuEPI if it is installed, otherwise emulated). Needs the patcher file in BepInEx/patchers and a game restart."); StubIgnore = ((BaseUnityPlugin)this).Config.Bind<string>("Bridge", "AzuEPIStubIgnore", "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot", "Assembly names (comma separated) of mods whose AzuEPI stub is left alone, because the UI mod already supports them itself. Remove a name to route that mod too. Needs a game restart."); AzuBridge.NameOf = PluginName; SlotOrigins.Fallback = delegate(string slot) { Assembly assembly = StubBridge.OriginOf(slot); return (!(assembly != null)) ? null : PluginName(assembly); }; if (StubIgnore.Value == "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Backpacks,RustyBags,Hunter_Legacy,Magic_Supremacy,MagicPlugin,Wizardry" || StubIgnore.Value == "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot,Magic_Supremacy") { StubIgnore.Value = "Jewelcrafting,BowsBeforeHoes,BetterArchery,AdventureBackpacks,EpicLoot"; } Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verbose", false, "Log every routed notice and dialog."); SceneManager.activeSceneChanged += OnScene; _harmony = new Harmony("seneaL.valheim.api"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)("SeneaL API 1.0.1 (contract " + 2 + ") loaded: " + string.Join(", ", Api.Features))); } private void Update() { float unscaledTime = Time.unscaledTime; if (unscaledTime >= _azuAt) { _azuAt = unscaledTime + 1f; TickAzu(); } if (unscaledTime >= _pollAt) { _pollAt = unscaledTime + 0.25f; if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { CustomSlots.Poll(Player.m_localPlayer); } } } private static void TickAzu() { if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuExtendedPlayerInventory", out var value) || !Object.op_Implicit((Object)(object)value.Instance)) { if (AzuBridge.Bound) { AzuBridge.Clear(); } } else if (!BridgeAzu.Value || CustomSlots.AzuManaged) { if (AzuBridge.Bound) { AzuBridge.Clear(); } } else if (AzuBridge.Bind(((object)value.Instance).GetType().Assembly.GetType("AzuEPI.API"))) { AzuBridge.Sync(); } } private void Start() { StubHost.Start(); } private void OnDestroy() { SceneManager.activeSceneChanged -= OnScene; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private static void OnScene(Scene from, Scene to) { Dialogs.DropStale(); } internal static void Trace(string text) { if (Verbose != null && Verbose.Value) { Log.LogInfo((object)text); } } internal static void Warn(string text) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)text); } } internal static void Fail(string where, Exception e) { Log.LogWarning((object)(where + ": " + e.GetType().Name + ": " + e.Message)); } private static string PluginName(Assembly asm) { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (Object.op_Implicit((Object)(object)value.Instance) && ((object)value.Instance).GetType().Assembly == asm) { return value.Metadata.Name; } } return asm.GetName().Name; } } public sealed class ModSetting { public string Owner { get; } public string Mod { get; } public ConfigEntryBase Entry { get; } public Func<string> Label { get; set; } public Func<string> Tip { get; set; } public Func<string[]> Options { get; set; } public Func<bool> Visible { get; set; } public string Group { get; set; } public bool Hold { get; set; } public string Text { get { try { string text = Label?.Invoke(); return string.IsNullOrEmpty(text) ? Entry.Definition.Key : text; } catch (Exception e) { Plugin.Fail("ModSetting label " + Owner, e); return Entry.Definition.Key; } } } public bool Shown { get { try { return Visible == null || Visible(); } catch (Exception e) { Plugin.Fail("ModSetting visible " + Owner, e); return true; } } } internal ModSetting(string owner, string mod, ConfigEntryBase entry) { Owner = owner; Mod = mod; Entry = entry; } } public static class ModSettings { private static readonly List<ModSetting> Items = new List<ModSetting>(16); public static IReadOnlyList<ModSetting> All => Items; public static event Action Changed; public static ModSetting Add(string owner, string mod, ConfigEntryBase entry, Func<string> label = null, Func<string> tip = null) { if (string.IsNullOrEmpty(owner)) { throw new ArgumentException("owner is required", "owner"); } if (entry == null) { throw new ArgumentNullException("entry"); } for (int num = Items.Count - 1; num >= 0; num--) { if (Items[num].Owner == owner && Items[num].Entry == entry) { Items.RemoveAt(num); } } ModSetting modSetting = new ModSetting(owner, string.IsNullOrEmpty(mod) ? owner : mod, entry) { Label = label, Tip = tip }; Items.Add(modSetting); Raise(); return modSetting; } public static int RemoveAll(string owner) { int num = Items.RemoveAll((ModSetting s) => s.Owner == owner); if (num > 0) { Raise(); } return num; } public static void Refresh() { Raise(); } private static void Raise() { Action changed = ModSettings.Changed; if (changed == null) { return; } Delegate[] invocationList = changed.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(); } catch (Exception e) { Plugin.Fail("ModSettings.Changed", e); } } } } public static class CustomSlots { private sealed class Entry { public SlotDef Def; public int Serial; public ItemData Seen; } private static readonly Slot<SlotProvider> Providers = new Slot<SlotProvider>(); private static readonly List<Entry> Items = new List<Entry>(8); private static SlotProvider _fed; internal static Func<Player> LocalPlayer = () => Player.m_localPlayer; private static int _serial; public static bool HasProvider => Providers.Current != null; public static bool AzuManaged { get; set; } public static event Action<SlotChange> Changed; public static void Register(string owner, SlotProvider provider, int priority = 0) { Providers.Set(owner, provider, priority); Feed(); } public static bool Unregister(string owner) { if (!Providers.Remove(owner)) { return false; } Feed(); return true; } public static SlotDef Add(string owner, SlotDef slot) { if (string.IsNullOrEmpty(owner)) { throw new ArgumentException("owner is required", "owner"); } if (slot == null) { throw new ArgumentNullException("slot"); } Remove(owner, slot.Id); if (slot.Mode == SlotMode.Linked && slot.Source == null) { throw new ArgumentException("a Linked slot needs a Source", "slot"); } slot.Owner = owner; Items.Add(new Entry { Def = slot, Serial = ++_serial }); Notify(Providers.Current, slot, added: true); return slot; } public static bool Remove(string owner, string id) { for (int i = 0; i < Items.Count; i++) { SlotDef def = Items[i].Def; if (!(def.Owner != owner) && !(def.Id != id)) { Items.RemoveAt(i); Notify(Providers.Current, def, added: false); return true; } } return false; } public static int RemoveAll(string owner) { int num = 0; for (int num2 = Items.Count - 1; num2 >= 0; num2--) { if (!(Items[num2].Def.Owner != owner)) { SlotDef def = Items[num2].Def; Items.RemoveAt(num2); Notify(Providers.Current, def, added: false); num++; } } return num; } public static string OriginOf(string slotName) { return SlotOrigins.Get(slotName); } public static SlotDef Find(string owner, string id) { for (int i = 0; i < Items.Count; i++) { SlotDef def = Items[i].Def; if (def.Owner == owner && def.Id == id) { return def; } } return null; } public static SlotDef[] Snapshot() { List<Entry> list = new List<Entry>(Items); list.Sort(Compare); SlotDef[] array = new SlotDef[list.Count]; for (int i = 0; i < array.Length; i++) { array[i] = list[i].Def; } return array; } public static ItemData Get(string owner, string id) { SlotDef slotDef = Find(owner, id); SlotProvider current = Providers.Current; if (slotDef == null) { return null; } if (slotDef.Mode == SlotMode.Linked) { return Read(slotDef, LocalPlayer()); } if (current == null) { return null; } try { return current.Get(slotDef); } catch (Exception e) { Plugin.Fail("SlotProvider " + Providers.Owner, e); return null; } } public static bool Set(string owner, string id, ItemData item) { SlotDef slotDef = Find(owner, id); SlotProvider current = Providers.Current; if (slotDef == null) { return false; } if (slotDef.Mode == SlotMode.Linked) { return Wear(slotDef, item); } if (current == null) { return false; } try { return current.Set(slotDef, item); } catch (Exception e) { Plugin.Fail("SlotProvider " + Providers.Owner, e); return false; } } internal static ItemData Read(SlotDef d, Player player) { try { return d.Source(player); } catch (Exception e) { Plugin.Fail("slot source " + d.Key, e); return null; } } private static bool Wear(SlotDef d, ItemData item) { Player val = LocalPlayer(); if (!Object.op_Implicit((Object)(object)val)) { return false; } ItemData val2 = Read(d, val); if (item == null) { if (val2 == null) { return true; } if (d.Take != null) { return Hand(d, d.Take, val, val2, "take"); } ((Humanoid)val).UnequipItem(val2, true); return true; } if (item == val2) { return true; } if (!d.Allows(item)) { return false; } if (d.Put != null) { return Hand(d, d.Put, val, item, "put"); } if (val2 != null) { ((Humanoid)val).UnequipItem(val2, true); } return ((Humanoid)val).EquipItem(item, true); } private static bool Hand(SlotDef d, Func<Player, ItemData, bool> f, Player player, ItemData item, string what) { try { return f(player, item); } catch (Exception e) { Plugin.Fail("slot " + what + " " + d.Key, e); return false; } } internal static void Poll(Player player) { for (int i = 0; i < Items.Count; i++) { Entry entry = Items[i]; if (entry.Def.Mode == SlotMode.Linked) { ItemData val = Read(entry.Def, player); if (val != entry.Seen) { ItemData seen = entry.Seen; entry.Seen = val; Report(new SlotChange(entry.Def, seen, val)); } } } } public static void Report(SlotChange change) { if (change == null) { throw new ArgumentNullException("change"); } Call(change.Slot.Changed, change); Action<SlotChange> changed = CustomSlots.Changed; if (changed != null) { Delegate[] invocationList = changed.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Call((Action<SlotChange>)invocationList[i], change); } } } private static void Call(Action<SlotChange> a, SlotChange c) { if (a == null) { return; } try { a(c); } catch (Exception e) { Plugin.Fail("slot callback " + c.Slot.Key, e); } } private static int Compare(Entry a, Entry b) { int num = a.Def.Order.CompareTo(b.Def.Order); if (num == 0) { return a.Serial.CompareTo(b.Serial); } return num; } private static void Feed() { SlotProvider current = Providers.Current; if (current == _fed) { return; } _fed = current; if (current != null) { SlotDef[] array = Snapshot(); foreach (SlotDef d in array) { Notify(current, d, added: true); } } } private static void Notify(SlotProvider p, SlotDef d, bool added) { if (p == null) { return; } try { if (added) { p.Added(d); } else { p.Removed(d); } } catch (Exception e) { Plugin.Fail("SlotProvider " + Providers.Owner, e); } } } public enum SlotMode { Stored, Worn, Linked } public enum SlotPlace { Auto, Row, Doll } public sealed class SlotDef { public bool EquipOnUse { get; set; } = true; public float EquipTime { get; set; } = -1f; public SlotPlace Place { get; set; } public string Id { get; } public string Owner { get; internal set; } public string Key => Owner + "/" + Id; public string Title { get; set; } public string Hint { get; set; } public string Origin { get; set; } public Sprite Icon { get; set; } public string IconName { get; set; } public SlotMode Mode { get; set; } public int Order { get; set; } public Func<Player, ItemData> Source { get; set; } public Func<ItemData, bool> Accepts { get; set; } public Func<bool> Visible { get; set; } public Func<Player, ItemData, bool> Put { get; set; } public Func<Player, ItemData, bool> Take { get; set; } public Action<SlotChange> Changed { get; set; } public SlotDef(string id) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException("id is required", "id"); } Id = id; } public bool Allows(ItemData item) { if (item != null) { if (Accepts != null) { return Accepts(item); } return true; } return false; } } public sealed class SlotChange { public SlotDef Slot { get; } public ItemData Old { get; } public ItemData New { get; } public SlotChange(SlotDef slot, ItemData old, ItemData next) { Slot = slot; Old = old; New = next; } } public abstract class SlotProvider { public abstract void Added(SlotDef slot); public virtual void Removed(SlotDef slot) { } public abstract ItemData Get(SlotDef slot); public abstract bool Set(SlotDef slot, ItemData item); } public enum ThemePart { Window, Inner, Card, Button, ButtonHover, Primary, PrimaryHover, Field, FieldFocus, Row, RowHover, RowOn, Tab, TabHover, TabOn, Track, Chip, Separator } public enum ThemeFont { Body, Strong, Title } public enum ThemeColor { Text, Muted, Accent, Good, Bad, Warn, OnPrimary, Shade } public abstract class ThemeProvider { public virtual float PixelsPerUnit => 0f; public virtual Sprite Sprite(ThemePart part) { return null; } public virtual TMP_FontAsset Font(ThemeFont font) { return null; } public virtual Sprite Icon(string name) { return null; } public virtual bool TryColor(ThemeColor color, out Color value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) value = default(Color); return false; } } public static class Themes { private static readonly Slot<ThemeProvider> Providers = new Slot<ThemeProvider>(); public static bool HasProvider => Providers.Current != null; public static string Owner => Providers.Owner; public static float PixelsPerUnit { get { ThemeProvider current = Providers.Current; if (current == null) { return 0f; } try { return current.PixelsPerUnit; } catch (Exception e) { Plugin.Fail("ThemeProvider " + Providers.Owner, e); return 0f; } } } public static event Action Changed; public static void Register(string owner, ThemeProvider provider, int priority = 0) { ThemeProvider current = Providers.Current; Providers.Set(owner, provider, priority); if (current != Providers.Current || provider == Providers.Current) { Refresh(); } } public static bool Unregister(string owner) { ThemeProvider current = Providers.Current; if (!Providers.Remove(owner)) { return false; } if (current != Providers.Current) { Refresh(); } return true; } public static void Refresh() { Action changed = Themes.Changed; if (changed == null) { return; } Delegate[] invocationList = changed.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(); } catch (Exception e) { Plugin.Fail("Themes.Changed", e); } } } public static Sprite Sprite(ThemePart part) { ThemeProvider current = Providers.Current; if (current == null) { return null; } try { return current.Sprite(part); } catch (Exception e) { Plugin.Fail("ThemeProvider " + Providers.Owner, e); return null; } } public static Sprite Icon(string name) { ThemeProvider current = Providers.Current; if (current == null || string.IsNullOrEmpty(name)) { return null; } try { return current.Icon(name); } catch (Exception e) { Plugin.Fail("ThemeProvider " + Providers.Owner, e); return null; } } public static TMP_FontAsset Font(ThemeFont font) { ThemeProvider current = Providers.Current; if (current == null) { return null; } try { return current.Font(font); } catch (Exception e) { Plugin.Fail("ThemeProvider " + Providers.Owner, e); return null; } } public static bool TryColor(ThemeColor color, out Color value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) value = default(Color); ThemeProvider current = Providers.Current; if (current == null) { return false; } try { return current.TryColor(color, out value); } catch (Exception e) { Plugin.Fail("ThemeProvider " + Providers.Owner, e); return false; } } } }