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 Blot v0.3.1
Blot.dll
Decompiled 8 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.GUI; using Jotunn.Managers; using Jotunn.Utils; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Blot")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.1.0")] [assembly: AssemblyInformationalVersion("0.3.1")] [assembly: AssemblyProduct("Blot")] [assembly: AssemblyTitle("Blot")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.1.0")] [module: UnverifiableCode] namespace Blot { [BepInPlugin("com.taeguk.valheim.blot", "Blot", "0.3.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class BlotPlugin : BaseUnityPlugin { public const string Guid = "com.taeguk.valheim.blot"; public const string ModName = "Blot"; public const string Version = "0.3.1"; private Harmony _harmony; public static BlotPlugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static bool IsHeadless { get; private set; } private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; try { IsHeadless = (int)SystemInfo.graphicsDeviceType == 4; } catch { IsHeadless = false; } BlotConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("com.taeguk.valheim.blot"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); OfferingCapture.Apply(_harmony); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BlotCommand()); Log.LogInfo((object)("Blot 0.3.1 loaded (" + (IsHeadless ? "headless server" : "game client") + ").")); } private void Update() { try { BlotServer.Tick(); } catch (Exception arg) { Log.LogError((object)$"Server tick: {arg}"); } try { BlotClient.Tick(); } catch (Exception arg2) { Log.LogError((object)$"Client tick: {arg2}"); } try { BlotGui.Tick(); } catch (Exception arg3) { Log.LogError((object)$"GUI tick: {arg3}"); } } private void OnDestroy() { try { BlotServer.SaveAll(); } catch { } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class BlotClient { public class RiteInfo { public string Name; public int Cost; public bool GateOpen; public string Blocker; } public class BlessingInfo { public string Name; public string Effect; public float SecondsLeft; } public class ClientState { public bool Received; public int Pool; public int Offered; public int MilestoneEvery; public List<RiteInfo> Rites = new List<RiteInfo>(); public List<BlessingInfo> Blessings = new List<BlessingInfo>(); public bool VoteOpen; public int VoteId; public string VoteRite = ""; public string VoteProposer = ""; public float VoteSeconds; public int VoteYes; public int VoteNo; public int VoteEligible; public bool CanVote; public bool HaveVoted; public int MyOffered; public int CapPoints; public int CapUsed; public float ReceivedAt; } [HarmonyPatch(typeof(Incinerator), "GetLeverHoverText")] private static class Patch_LeverHover { private static void Postfix(ref string __result) { try { __result += LeverText(); } catch { } } } [HarmonyPatch(typeof(Switch), "Interact")] private static class Patch_SwitchInteract { private static bool Prefix(Switch __instance, bool hold, bool alt, ref bool __result) { try { if ((!alt && !ZInput.GetKey((KeyCode)308, true) && !ZInput.GetKey((KeyCode)307, true)) || hold || (Object)(object)((Component)__instance).GetComponentInParent<Incinerator>() == (Object)null) { return true; } BlotGui.OpenRites(); __result = true; return false; } catch { return true; } } } public static ClientState S = new ClientState(); public static void Tick() { if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || (Object)(object)Game.instance == (Object)null) { ResetSession(); } } private static void ResetSession() { if (S.Received) { S = new ClientState(); BlotGui.CloseAll(); } } public static void OnState(ZPackage pkg) { ClientState clientState = new ClientState { Received = true, ReceivedAt = Time.time }; if (pkg.ReadInt() >= 4) { clientState.Pool = pkg.ReadInt(); clientState.Offered = pkg.ReadInt(); clientState.MilestoneEvery = pkg.ReadInt(); int num = pkg.ReadInt(); for (int i = 0; i < num; i++) { clientState.Rites.Add(new RiteInfo { Name = pkg.ReadString(), Cost = pkg.ReadInt(), GateOpen = pkg.ReadBool(), Blocker = pkg.ReadString() }); } int num2 = pkg.ReadInt(); for (int j = 0; j < num2; j++) { clientState.Blessings.Add(new BlessingInfo { Name = pkg.ReadString(), Effect = pkg.ReadString(), SecondsLeft = pkg.ReadSingle() }); } clientState.VoteOpen = pkg.ReadBool(); if (clientState.VoteOpen) { clientState.VoteId = pkg.ReadInt(); clientState.VoteRite = pkg.ReadString(); clientState.VoteProposer = pkg.ReadString(); clientState.VoteSeconds = pkg.ReadSingle(); clientState.VoteYes = pkg.ReadInt(); clientState.VoteNo = pkg.ReadInt(); clientState.VoteEligible = pkg.ReadInt(); clientState.CanVote = pkg.ReadBool(); clientState.HaveVoted = pkg.ReadBool(); } clientState.MyOffered = pkg.ReadInt(); clientState.CapPoints = pkg.ReadInt(); clientState.CapUsed = pkg.ReadInt(); S = clientState; } } public static void OnReply(ZPackage pkg) { Print(pkg.ReadString()); } public static void OnNotice(ZPackage pkg) { bool num = pkg.ReadBool(); string text = pkg.ReadString(); if (num && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); } Print("[Blot] " + text, chatOnly: true); } public static void Print(string text, bool chatOnly = false) { if (string.IsNullOrEmpty(text)) { return; } string[] array = text.Split(new char[1] { '\n' }); foreach (string text2 in array) { try { if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(text2); } } catch { } if (chatOnly) { continue; } try { if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString(text2); } } catch { } } } public static string SendCommand(string text) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null) { return "Not connected to a world."; } ZPackage val = new ZPackage(); val.Write(text); Net.ToServer("Blot_Command", val); return null; } public static string LeverText() { if (!S.Received) { return ""; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"\n<color=#E0C078>Blot</color> - pool {S.Pool}"); foreach (RiteInfo rite in S.Rites) { bool flag = string.IsNullOrEmpty(rite.Blocker); stringBuilder.Append(" | " + rite.Name + " " + (flag ? "<color=#7CFC7C>ready</color>" : (rite.GateOpen ? $"{Math.Min(S.Pool, rite.Cost)}/{rite.Cost}" : "sealed"))); } if (S.CapPoints > 0) { stringBuilder.Append($"\nYour offerings today: {S.CapUsed}/{S.CapPoints}"); } stringBuilder.Append((ZInput.IsNonClassicFunctionality() && ZInput.IsGamepadActive()) ? "\n[<color=yellow><b>$KEY_AltKeys + $KEY_Use</b></color>] Rites" : "\n[<color=yellow><b>$KEY_AltPlace + $KEY_Use</b></color>] Rites"); if (Localization.instance == null) { return stringBuilder.ToString(); } return Localization.instance.Localize(stringBuilder.ToString()); } } internal class BlotCommand : ConsoleCommand { public override string Name => "blot"; public override string Help => "Blot - offerings, blessings and rites. `blot help` lists the commands."; public override void Run(string[] args) { string text = BlotClient.SendCommand("blot " + string.Join(" ", args)); if (text != null) { BlotClient.Print(text); } } } internal static class BlotGui { [HarmonyPatch(typeof(Menu), "Update")] private static class Patch_MenuUpdate { private static bool Prefix() { try { if (!Blocking || !ZInput.GetKeyDown((KeyCode)27, true)) { return true; } CloseRites(); return false; } catch { return true; } } } [HarmonyPatch(typeof(Player), "TakeInput")] private static class Patch_PlayerTakeInput { private static void Postfix(ref bool __result) { if (Blocking) { __result = false; } } } [HarmonyPatch(typeof(PlayerController), "TakeInput")] private static class Patch_ControllerTakeInput { private static void Postfix(ref bool __result) { if (Blocking) { __result = false; } } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__24_0; public static UnityAction <>9__24_1; internal void <BuildVote>b__24_0() { Cast(yes: true); } internal void <BuildVote>b__24_1() { Cast(yes: false); } } private static bool _ritesOpen; private static int _confirm = -1; private static int _dismissedVote = -1; private static GameObject _rites; private static GameObject _vote; private static object _ritesBuiltFor; private static object _voteBuiltFor; private static int _ritesBuiltConfirm = -2; private static float _voteBuiltAt; private static bool _blocked; public static bool Blocking => _ritesOpen; private static bool VoteVisible { get { BlotClient.ClientState s = BlotClient.S; if (s.VoteOpen && s.CanVote && !s.HaveVoted) { return _dismissedVote != s.VoteId; } return false; } } public static void OpenRites() { _ritesOpen = true; _confirm = -1; _ritesBuiltFor = null; } public static void CloseAll() { _ritesOpen = false; Destroy(ref _rites); Destroy(ref _vote); SetBlock(block: false); } private static void CloseRites() { _ritesOpen = false; Destroy(ref _rites); } public static void Tick() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) if (BlotPlugin.IsHeadless) { return; } if ((Object)(object)Player.m_localPlayer == (Object)null || !UiKit.Ready) { if ((Object)(object)_rites != (Object)null || (Object)(object)_vote != (Object)null || _blocked) { CloseAll(); } return; } if (_ritesOpen) { if ((Object)(object)_rites == (Object)null) { _rites = UiKit.Panel("BlotRites", 560f, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero); _ritesBuiltFor = null; } if (_ritesBuiltFor != BlotClient.S || _ritesBuiltConfirm != _confirm) { BuildRites(); } } else { Destroy(ref _rites); } if (VoteVisible) { if ((Object)(object)_vote == (Object)null) { _vote = UiKit.Panel("BlotVote", 400f, new Vector2(1f, 0.75f), new Vector2(1f, 1f), new Vector2(-40f, 0f)); _voteBuiltFor = null; } if (_voteBuiltFor != BlotClient.S || Time.time - _voteBuiltAt > 1f) { BuildVote(); } } else { Destroy(ref _vote); } SetBlock(Blocking); } private static void SetBlock(bool block) { if (block == _blocked) { return; } _blocked = block; try { GUIManager.BlockInput(block); } catch { } } private static void Destroy(ref GameObject go) { if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } go = null; } private static void BuildRites() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Expected O, but got Unknown BlotClient.ClientState clientState = (BlotClient.ClientState)(_ritesBuiltFor = BlotClient.S); _ritesBuiltConfirm = _confirm; UiKit.Clear(_rites); Transform transform = _rites.transform; UiKit.Header(transform, "Rites of the Blot"); if (!clientState.Received) { UiKit.Body(transform, "No word from the server yet."); UiKit.Button(transform, "Close", new UnityAction(CloseRites)); return; } UiKit.Body(transform, $"Pool {UiKit.Orange(clientState.Pool.ToString())} Offered {clientState.Offered}"); UiKit.Small(transform, string.Format("Your offerings: {0}{1}", clientState.MyOffered, (clientState.CapPoints > 0) ? $", {clientState.CapUsed}/{clientState.CapPoints} today" : "")); UiKit.Space(transform, 8f); for (int i = 0; i < clientState.Rites.Count; i++) { BlotClient.RiteInfo r = clientState.Rites[i]; int index = i; bool flag = string.IsNullOrEmpty(r.Blocker); Transform parent = UiKit.Row(transform); UiKit.Grow((Component)(object)UiKit.Body(parent, string.Format("{0} - cost {1}\n{2}", UiKit.Orange(r.Name), r.Cost, flag ? "Ready" : UiKit.Grey(r.Blocker)))); bool interactable = flag && !clientState.VoteOpen; if (_confirm == index) { UiKit.Button(parent, "Confirm", (UnityAction)delegate { BlotClient.SendCommand("blot rite " + r.Name.ToLowerInvariant()); _confirm = -1; CloseRites(); }, interactable, 130f); } else { UiKit.Button(parent, "Perform", (UnityAction)delegate { _confirm = index; }, interactable, 130f); } if (_confirm == index) { UiKit.Small(transform, $"If the vote passes this spends {r.Cost} of the pool ({clientState.Pool})."); } } UiKit.Space(transform, 8f); if (clientState.Blessings.Count == 0) { UiKit.Small(transform, UiKit.Grey("No blessing is upon the realm.")); } foreach (BlotClient.BlessingInfo blessing in clientState.Blessings) { UiKit.Small(transform, "Blessing: " + blessing.Name + " - " + blessing.Effect + ", " + BlotServer.Format(blessing.SecondsLeft) + " left"); } if (clientState.VoteOpen) { UiKit.Small(transform, $"Vote on {clientState.VoteRite}: {clientState.VoteYes} yes / {clientState.VoteNo} no of {clientState.VoteEligible}"); } UiKit.Space(transform, 10f); Transform parent2 = UiKit.Row(transform); UiKit.Grow((Component)(object)UiKit.Small(parent2, UiKit.Grey("Esc to close"))); UiKit.Button(parent2, "Close", new UnityAction(CloseRites), interactable: true, 130f); } private static void BuildVote() { //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown BlotClient.ClientState s = BlotClient.S; _voteBuiltFor = s; _voteBuiltAt = Time.time; UiKit.Clear(_vote); Transform transform = _vote.transform; UiKit.Header(transform, "Rite vote"); UiKit.Body(transform, $"{s.VoteProposer} proposes {UiKit.Orange(s.VoteRite)}.\nIt spends {VoteCost(s)} of the pool ({s.Pool})."); UiKit.Small(transform, $"{s.VoteYes} yes / {s.VoteNo} no of {s.VoteEligible} - {Math.Max(0f, s.VoteSeconds - (Time.time - s.ReceivedAt)):0} s"); Transform parent = UiKit.Row(transform); object obj = <>c.<>9__24_0; if (obj == null) { UnityAction val = delegate { Cast(yes: true); }; <>c.<>9__24_0 = val; obj = (object)val; } UiKit.Button(parent, "Yes", (UnityAction)obj, interactable: true, 104f); object obj2 = <>c.<>9__24_1; if (obj2 == null) { UnityAction val2 = delegate { Cast(yes: false); }; <>c.<>9__24_1 = val2; obj2 = (object)val2; } UiKit.Button(parent, "No", (UnityAction)obj2, interactable: true, 104f); UiKit.Button(parent, "Later", (UnityAction)delegate { _dismissedVote = s.VoteId; }, interactable: true, 104f); } private static int VoteCost(BlotClient.ClientState s) { foreach (BlotClient.RiteInfo rite in s.Rites) { if (rite.Name == s.VoteRite) { return rite.Cost; } } return 0; } private static void Cast(bool yes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(BlotClient.S.VoteId); val.Write(yes); Net.ToServer("Blot_Vote", val); _dismissedVote = BlotClient.S.VoteId; } } internal static class OfferingCapture { private static readonly Dictionary<Inventory, KeyValuePair<Incinerator, long>> Burning = new Dictionary<Inventory, KeyValuePair<Incinerator, long>>(); public static void Apply(Harmony h) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Incinerator), "Incinerate", (Type[])null, (Type[])null); MethodInfo methodInfo2 = ((methodInfo != null) ? AccessTools.EnumeratorMoveNext((MethodBase)methodInfo) : null); if (methodInfo2 == null) { BlotPlugin.Log.LogError((object)"Incinerator.Incinerate coroutine not found - offerings will not score."); return; } h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(OfferingCapture), "IncineratePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); h.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(OfferingCapture), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception arg) { BlotPlugin.Log.LogError((object)$"Offering capture patch failed - offerings will not score: {arg}"); } } private static void IncineratePrefix(Incinerator __instance, long uid) { try { Inventory val = (((Object)(object)__instance.m_container != (Object)null) ? __instance.m_container.GetInventory() : null); if (val != null) { Burning[val] = new KeyValuePair<Incinerator, long>(__instance, uid); } } catch { } } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo getInventory = AccessTools.Method(typeof(Container), "GetInventory", (Type[])null, (Type[])null); MethodInfo snapshot = AccessTools.Method(typeof(OfferingCapture), "Snapshot", (Type[])null, (Type[])null); bool done = false; foreach (CodeInstruction ci in instructions) { yield return ci; if (!done && (ci.opcode == OpCodes.Callvirt || ci.opcode == OpCodes.Call) && ci.operand as MethodInfo == getInventory) { yield return new CodeInstruction(OpCodes.Dup, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)snapshot); done = true; } } if (!done) { BlotPlugin.Log.LogError((object)"Offering capture: GetInventory call not found in Incinerate - offerings will not score."); } } public static void Snapshot(Inventory inv) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) try { if (inv == null || !Burning.TryGetValue(inv, out var value)) { return; } Burning.Remove(inv); Incinerator key = value.Key; if ((Object)(object)key == (Object)null || (Object)(object)key.m_nview == (Object)null || !key.m_nview.IsValid() || !key.m_nview.IsOwner() || ((Object)(object)key.m_container != (Object)null && key.m_container.IsInUse())) { return; } List<ItemData> allItems = inv.GetAllItems(); if (allItems.Count == 0) { return; } ZPackage val = new ZPackage(); val.Write(key.m_nview.GetZDO().m_uid); val.Write(value.Value); val.Write(allItems.Count); foreach (ItemData item in allItems) { string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : (item.m_shared?.m_name ?? "")); val.Write(text); val.Write(Math.Max(1, item.m_stack)); int num = 0; if (item.m_cheated) { num |= 1; } if (item.m_customData != null && item.m_customData.ContainsKey("valcoin")) { num |= 4; } val.Write(num); } Net.ToServer("Blot_Offering", val); } catch (Exception ex) { BlotPlugin.Log.LogWarning((object)("Offering snapshot failed: " + ex.Message)); } } } internal static class UiKit { private static bool _loggedPanel; private static InventoryGui Inv => InventoryGui.instance; public static bool Ready { get { if ((Object)(object)Inv != (Object)null && GUIManager.Instance != null) { return (Object)(object)GUIManager.CustomGUIFront != (Object)null; } return false; } } public static GameObject Panel(string name, float width, Vector2 anchor, Vector2 pivot, Vector2 position) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(GUIManager.CustomGUIFront.transform, false); RectTransform val2 = (RectTransform)val.transform; Vector2 anchorMin = (val2.anchorMax = anchor); val2.anchorMin = anchorMin; val2.pivot = pivot; val2.anchoredPosition = position; val2.sizeDelta = new Vector2(width, 200f); Image val4 = val.AddComponent<Image>(); Image val5 = PanelImage(); if ((Object)(object)val5 != (Object)null) { val4.sprite = val5.sprite; val4.type = val5.type; ((Graphic)val4).material = ((Graphic)val5).material; ((Graphic)val4).color = ((Graphic)val5).color; val4.pixelsPerUnitMultiplier = val5.pixelsPerUnitMultiplier; val4.fillCenter = val5.fillCenter; } else { GUIManager.Instance.ApplyWoodpanelStyle(val.transform); } VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>(); ((LayoutGroup)obj).padding = new RectOffset(26, 26, 22, 22); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 4f; ((LayoutGroup)obj).childAlignment = (TextAnchor)0; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ContentSizeFitter obj2 = val.AddComponent<ContentSizeFitter>(); obj2.verticalFit = (FitMode)2; obj2.horizontalFit = (FitMode)0; try { val.AddComponent<DragWindowCntrl>(); } catch { } return val; } private static Image PanelImage() { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Inv == (Object)null) { return null; } Image val = null; List<string> list = new List<string>(); Transform[] array = (Transform[])(object)new Transform[3] { (Transform)Inv.m_container, (Transform)Inv.m_player, ((Component)Inv).transform }; foreach (Transform val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } float num = 0f; Image[] componentsInChildren = ((Component)val2).GetComponentsInChildren<Image>(true); foreach (Image val3 in componentsInChildren) { if ((Object)(object)val3.sprite == (Object)null) { continue; } string text = ((Object)val3.sprite).name.ToLowerInvariant(); if (list.Count < 40 && !list.Contains(text)) { list.Add(text); } if (text.Contains("woodpanel") && !text.Contains("mask") && !(((Graphic)val3).color.a < 0.5f)) { Rect rect = ((Graphic)val3).rectTransform.rect; float num2 = Mathf.Abs(((Rect)(ref rect)).width * ((Rect)(ref rect)).height) + ((text == "woodpanel_container") ? 1E+09f : 0f); if ((Object)(object)val == (Object)null || num2 > num) { val = val3; num = num2; } } } if ((Object)(object)val != (Object)null) { break; } } if (!_loggedPanel) { _loggedPanel = true; ManualLogSource log = BlotPlugin.Log; object obj; if (!((Object)(object)val != (Object)null)) { obj = "No wood panel in the inventory - Blot panels use Jotunn's copy of the game's wood panel."; } else { object[] obj2 = new object[5] { ((Object)val.sprite).name, val.type, ((Graphic)val).color.a, null, null }; TMP_Text containerName = Inv.m_containerName; object obj3; if (containerName == null) { obj3 = null; } else { TMP_FontAsset font = containerName.font; obj3 = ((font != null) ? ((Object)font).name : null); } obj2[3] = obj3; TMP_Text containerName2 = Inv.m_containerName; obj2[4] = ((containerName2 != null) ? new float?(containerName2.fontSize) : ((float?)null)); obj = string.Format("Blot panels copy the inventory background '{0}' ({1}, alpha {2:0.##}); text styles from InventoryGui (title font '{3}', size {4}).", obj2); } log.LogInfo(obj); BlotPlugin.Log.LogInfo((object)("Inventory sprites seen: " + string.Join(", ", list))); } return val; } public static TMP_Text Header(Transform parent, string text) { return Text(parent, text, ((Object)(object)Inv != (Object)null) ? Inv.m_containerName : null, 1f); } public static TMP_Text Body(Transform parent, string text) { return Text(parent, text, ((Object)(object)Inv != (Object)null) ? (Inv.m_recipeDecription ?? Inv.m_weight) : null, 1f); } public static TMP_Text Small(Transform parent, string text) { return Text(parent, text, ((Object)(object)Inv != (Object)null) ? (Inv.m_weight ?? Inv.m_recipeDecription) : null, 1f); } private static TMP_Text Text(Transform parent, string text, TMP_Text style, float scale) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_006d: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("text", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); if ((Object)(object)style != (Object)null) { ((TMP_Text)val2).font = style.font; ((TMP_Text)val2).fontSharedMaterial = style.fontSharedMaterial; ((TMP_Text)val2).fontSize = style.fontSize * scale; ((Graphic)val2).color = ((Graphic)style).color; ((TMP_Text)val2).fontStyle = style.fontStyle; ((TMP_Text)val2).characterSpacing = style.characterSpacing; } else { ((TMP_Text)val2).fontSize = 18f; ((Graphic)val2).color = GUIManager.Instance.ValheimBeige; } ((TMP_Text)val2).enableAutoSizing = false; ((TMP_Text)val2).richText = true; ((TMP_Text)val2).enableWordWrapping = true; ((TMP_Text)val2).alignment = (TextAlignmentOptions)257; ((Graphic)val2).raycastTarget = false; ((TMP_Text)val2).text = text ?? ""; return (TMP_Text)(object)val2; } public static Button Button(Transform parent, string label, UnityAction onClick, bool interactable = true, float width = 0f) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) Button val = (((Object)(object)Inv != (Object)null) ? Inv.m_takeAllButton : null); GameObject val2; if ((Object)(object)val != (Object)null) { val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent, false); ((Object)val2).name = "button"; UIGamePad component = val2.GetComponent<UIGamePad>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } UIGamePad[] componentsInChildren = val2.GetComponentsInChildren<UIGamePad>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)componentsInChildren[i]); } val2.SetActive(true); } else { val2 = GUIManager.Instance.CreateButton(label, parent, Vector2.zero, Vector2.zero, Vector2.zero, 160f, 32f); } Button component2 = val2.GetComponent<Button>(); component2.onClick = new ButtonClickedEvent(); if (onClick != null) { ((UnityEvent)component2.onClick).AddListener(onClick); } ((Selectable)component2).interactable = interactable; TMP_Text componentInChildren = val2.GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = label; } else { Text componentInChildren2 = val2.GetComponentInChildren<Text>(true); if ((Object)(object)componentInChildren2 != (Object)null) { componentInChildren2.text = label; } } LayoutElement val3 = val2.GetComponent<LayoutElement>() ?? val2.AddComponent<LayoutElement>(); float num; if (!((Object)(object)val != (Object)null)) { num = 32f; } else { Rect rect = ((RectTransform)((Component)val).transform).rect; num = Mathf.Max(28f, ((Rect)(ref rect)).height); } float num2 = num; float minHeight = (val3.preferredHeight = num2); val3.minHeight = minHeight; if (width > 0f) { minHeight = (val3.preferredWidth = width); val3.minWidth = minHeight; val3.flexibleWidth = 0f; } return component2; } public static Transform Row(Transform parent, float spacing = 8f) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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) GameObject val = new GameObject("row", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = spacing; ((LayoutGroup)obj).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; return val.transform; } public static void Grow(Component c) { (c.GetComponent<LayoutElement>() ?? c.gameObject.AddComponent<LayoutElement>()).flexibleWidth = 1f; } public static void Space(Transform parent, float h) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("space", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); LayoutElement obj = val.AddComponent<LayoutElement>(); float minHeight = (obj.preferredHeight = h); obj.minHeight = minHeight; } public static void Clear(GameObject panel) { if (!((Object)(object)panel == (Object)null)) { for (int num = panel.transform.childCount - 1; num >= 0; num--) { GameObject gameObject = ((Component)panel.transform.GetChild(num)).gameObject; gameObject.SetActive(false); Object.Destroy((Object)(object)gameObject); } } } public static string Orange(string s) { return "<color=#FFA500>" + s + "</color>"; } public static string Grey(string s) { return "<color=#A0A0A0>" + s + "</color>"; } } public class Who { public string Account = ""; public string Name = ""; public long Uid; public bool IsHost; public ZNetPeer Peer; public long PlayerId { get { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { if (!((Object)(object)Player.m_localPlayer != (Object)null)) { return 0L; } return Player.m_localPlayer.GetPlayerID(); } if (Peer == null) { return 0L; } try { if (!((ZDOID)(ref Peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(Peer.m_characterID); if (zDO != null) { long num = zDO.GetLong(ZDOVars.s_playerID, 0L); if (num != 0L) { return num; } } } } catch { } return Peer.m_playerID; } } public Vector3 Position { get { //IL_0079: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { if (!((Object)(object)Player.m_localPlayer != (Object)null)) { return Vector3.zero; } return ((Component)Player.m_localPlayer).transform.position; } if (Peer == null) { return Vector3.zero; } try { if (!((ZDOID)(ref Peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(Peer.m_characterID); if (zDO != null) { return zDO.GetPosition(); } } } catch { } return Peer.GetRefPos(); } } } internal static class Accounts { public static bool IsLocalHost { get { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return !BlotPlugin.IsHeadless; } return false; } } public static string Normalize(string id) { if (string.IsNullOrEmpty(id)) { return ""; } id = id.Trim(); if (!id.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase)) { return id; } return id.Substring(6); } public static string OfPeer(ZNetPeer peer) { try { return (peer?.m_socket != null) ? Normalize(peer.m_socket.GetHostName()) : ""; } catch { return ""; } } public static string Local() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) try { return Normalize(((object)((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString()); } catch { return ""; } } public static Who FromSender(long sender) { if ((Object)(object)ZNet.instance == (Object)null) { return null; } if (ZRoutedRpc.instance != null && sender == ZRoutedRpc.instance.m_id) { if (!IsLocalHost) { return null; } return new Who { Account = Local(), Name = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetName() : "Host"), Uid = sender, IsHost = true }; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer != null) { return FromPeer(peer); } return null; } public static Who FromPeer(ZNetPeer peer) { return new Who { Account = OfPeer(peer), Name = (peer.m_playerName ?? ""), Uid = peer.m_uid, Peer = peer }; } public static List<Who> Online() { List<Who> list = new List<Who>(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return list; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone()) { list.Add(FromPeer(peer)); } } if (IsLocalHost && (Object)(object)Player.m_localPlayer != (Object)null && ZRoutedRpc.instance != null) { list.Add(FromSender(ZRoutedRpc.instance.m_id)); } return list; } public static Who FindOnline(string query) { if (string.IsNullOrWhiteSpace(query)) { return null; } query = query.Trim(); foreach (Who item in Online()) { if (item.Account == Normalize(query) || string.Equals(item.Name, query, StringComparison.OrdinalIgnoreCase)) { return item; } } return null; } public unsafe static string OfPlayer(Player player) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //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_006a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return ""; } if ((Object)(object)player == (Object)(object)Player.m_localPlayer) { return Local(); } try { ZDOID zDOID = ((Character)player).GetZDOID(); foreach (PlayerInfo player2 in ZNet.instance.GetPlayerList()) { if (player2.m_characterID == zDOID) { PlatformUserID id = player2.m_userInfo.m_id; return Normalize(((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString()); } } } catch { } return ""; } } internal static class BlotConfig { public static ConfigEntry<int> RestockCost; public static ConfigEntry<int> RegrowthCost; public static ConfigEntry<string> RestockGate; public static ConfigEntry<string> RegrowthGate; public static ConfigEntry<int> RiteMinContribution; public static ConfigEntry<bool> RiteVote; public static ConfigEntry<int> RiteVoteSeconds; public static ConfigEntry<int> RiteVoteCooldownMinutes; public static ConfigEntry<string> RestockSkipLocations; public static ConfigEntry<string> RegrowthProtectPrefabs; public static ConfigEntry<float> OccupiedRadius; public static ConfigEntry<int> MaxZdoOpsPerFrame; public static ConfigEntry<bool> DailyCapEnabled; public static ConfigEntry<bool> DailyCapIncludesBossOfferings; public static ConfigEntry<int> DailyCapPoints; public static ConfigEntry<int> DailyCapResetHour; public static ConfigEntry<int> MilestoneEvery; public static ConfigEntry<bool> OnlineOnlyTimers; public static ConfigEntry<string> DiscordWebhookUrl; public static ConfigEntry<string> DiscordWebhookUrlAdmin; public static ConfigEntry<bool> PreferServerGuard; public static void Bind(ConfigFile c) { RestockCost = c.Bind<int>("Rites", "restockCost", 250, "Pool points Restock costs. Performing a rite spends its cost; the rest of the pool stays."); RestockGate = c.Bind<string>("Rites", "restockGate", "defeated_gdking", "World key that must be set for Restock (The Elder)."); RegrowthCost = c.Bind<int>("Rites", "regrowthCost", 750, "Pool points Regrowth costs."); RegrowthGate = c.Bind<string>("Rites", "regrowthGate", "defeated_goblinking", "World key that must be set for Regrowth (Yagluth)."); RiteMinContribution = c.Bind<int>("Rites", "riteMinContribution", 10, "Points an account must have offered (in total) to propose or vote on a rite."); RiteVote = c.Bind<bool>("Rites", "riteVote", true, "A rite proposal needs yes from more than half of the online contributors."); RiteVoteSeconds = c.Bind<int>("Rites", "riteVoteSeconds", 120, "How long a rite vote stays open."); RiteVoteCooldownMinutes = c.Bind<int>("Rites", "riteVoteCooldownMinutes", 10, "A proposer whose vote failed waits this long to propose again."); RestockSkipLocations = c.Bind<string>("Rites", "restockSkipLocations", "StartTemple,Vendor_BlackForest,Hildir_camp,BogWitch_Camp,Hildir_crypt,Hildir_cave,Hildir_plainsfortress", "Location prefabs Restock never touches (comma separated)."); RegrowthProtectPrefabs = c.Bind<string>("Rites", "regrowthProtectPrefabs", "Player_tombstone", "Extra prefabs that protect their zone from Regrowth, besides anything a player built and tamed creatures."); OccupiedRadius = c.Bind<float>("Rites", "occupiedRadius", 300f, "A location or zone within this many metres of a player is left for later."); MaxZdoOpsPerFrame = c.Bind<int>("Rites", "maxZdoOpsPerFrame", 500, "World objects Restock/Regrowth process per frame."); DailyCapEnabled = c.Bind<bool>("Offerings", "dailyCapEnabled", true, "Anti-farm cap per account and day."); DailyCapPoints = c.Bind<int>("Offerings", "dailyCapPoints", 200, "Points an account can score per day."); DailyCapResetHour = c.Bind<int>("Offerings", "dailyCapResetHour", 0, "Server-local hour the day rolls over."); DailyCapIncludesBossOfferings = c.Bind<bool>("Offerings", "dailyCapIncludesBossOfferings", false, "Count boss/Lord full-value offerings against the cap."); MilestoneEvery = c.Bind<int>("Blessings", "milestoneEvery", 150, "A blessing is granted each time the realm's total offered points cross a multiple of this."); OnlineOnlyTimers = c.Bind<bool>("Blessings", "onlineOnlyTimers", true, "Blessing timers count down only while someone is online."); DiscordWebhookUrl = c.Bind<string>("Discord", "discordWebhookUrl", "", "Public webhook (used when ServerGuard 2.2 is not present)."); DiscordWebhookUrlAdmin = c.Bind<string>("Discord", "discordWebhookUrlAdmin", "", "Admin webhook (falls back to the public one)."); PreferServerGuard = c.Bind<bool>("Discord", "preferServerGuard", true, "Post through ServerGuard's webhooks when ServerGuard 2.2 is loaded."); } } internal static class Net { [HarmonyPatch(typeof(ZNet), "Awake")] private static class Patch_ZNet_Awake { private static void Postfix() { try { Register(); } catch (Exception arg) { BlotPlugin.Log.LogError((object)$"RPC registration failed: {arg}"); } } } public const string Offering = "Blot_Offering"; public const string Command = "Blot_Command"; public const string Vote = "Blot_Vote"; public const string State = "Blot_State"; public const string Reply = "Blot_Reply"; public const string Notice = "Blot_Notice"; public static long ServerId { get { if (ZRoutedRpc.instance == null) { return 0L; } return ZRoutedRpc.instance.GetServerPeerID(); } } public static void ToServer(string rpc, ZPackage pkg) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ServerId, rpc, new object[1] { pkg }); } } public static void ToClient(long uid, string rpc, ZPackage pkg) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(uid, rpc, new object[1] { pkg }); } } public static void NoticeTo(long uid, string text, bool center = true) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(center); val.Write(text ?? ""); if (uid == 0L) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(0L, "Blot_Notice", new object[1] { val }); } } else { ToClient(uid, "Blot_Notice", val); } } private static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; instance.Register<ZPackage>("Blot_Offering", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotServer.OnOffering(s, p); }); }); instance.Register<ZPackage>("Blot_Command", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotServer.OnCommand(s, p); }); }); instance.Register<ZPackage>("Blot_Vote", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotServer.OnVote(s, p); }); }); instance.Register<ZPackage>("Blot_State", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotClient.OnState(p); }); }); instance.Register<ZPackage>("Blot_Reply", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotClient.OnReply(p); }); }); instance.Register<ZPackage>("Blot_Notice", (Action<long, ZPackage>)delegate(long s, ZPackage p) { Guard(delegate { BlotClient.OnNotice(p); }); }); } private static void Guard(Action a) { try { a(); } catch (Exception arg) { BlotPlugin.Log.LogError((object)$"RPC handler: {arg}"); } } } internal static class YamlIO { private static readonly ISerializer Ser = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling((DefaultValuesHandling)0).Build(); private static readonly IDeserializer De = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); public static T Load<T>(string path) where T : class, new() { try { if (!File.Exists(path)) { return new T(); } string text = File.ReadAllText(path); if (string.IsNullOrWhiteSpace(text)) { return new T(); } return De.Deserialize<T>(text) ?? new T(); } catch (Exception ex) { BlotPlugin.Log.LogError((object)("Could not read " + path + ": " + ex.Message + ". Keeping a copy as .broken and starting empty.")); try { File.Copy(path, path + ".broken", overwrite: true); } catch { } return new T(); } } public static bool TryLoad<T>(string path, out T value, out string error) where T : class, new() { value = null; error = null; try { string text = File.ReadAllText(path); value = (string.IsNullOrWhiteSpace(text) ? new T() : (De.Deserialize<T>(text) ?? new T())); return true; } catch (Exception ex) { error = ex.Message.Replace('\r', ' ').Replace('\n', ' '); return false; } } public static T LoadOrNull<T>(string path) where T : class, new() { if (!File.Exists(path)) { return null; } return Load<T>(path); } public static void Save<T>(string path, T value, string header = null) { SaveText(path, header + Ser.Serialize((object)value)); } public static void SaveText(string path, string text) { try { Directory.CreateDirectory(Path.GetDirectoryName(path)); string text2 = path + ".tmp"; File.WriteAllText(text2, text); if (File.Exists(path)) { File.Replace(text2, path, null); } else { File.Move(text2, path); } } catch (Exception ex) { BlotPlugin.Log.LogError((object)("Could not write " + path + ": " + ex.Message)); } } public static void SaveBytes(string path, byte[] data) { try { Directory.CreateDirectory(Path.GetDirectoryName(path)); string text = path + ".tmp"; File.WriteAllBytes(text, data); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } catch (Exception ex) { BlotPlugin.Log.LogError((object)("Could not write " + path + ": " + ex.Message)); } } } public class BlotState { public int Pool { get; set; } public int Offered { get; set; } public int MilestonesGranted { get; set; } public List<ActiveBlessing> Blessings { get; set; } = new List<ActiveBlessing>(); public List<string> RitesAnnouncedReady { get; set; } = new List<string>(); public List<string> RitesPerformed { get; set; } = new List<string>(); public List<string> Log { get; set; } = new List<string>(); public ActiveBlessing Permanent { get; set; } public string Difficulty { get; set; } = ""; public List<string> DifficultyOriginal { get; set; } } public class ActiveBlessing { public string Id { get; set; } = ""; public double SecondsLeft { get; set; } public string Key { get; set; } = ""; public bool HadOriginal { get; set; } public string Original { get; set; } = ""; } public class ContributionsDoc { public Dictionary<string, AccountContribution> Accounts { get; set; } = new Dictionary<string, AccountContribution>(); } public class AccountContribution { public string Name { get; set; } = ""; [YamlMember(Alias = "allTime")] public int Total { get; set; } public string CapDay { get; set; } = ""; public int CapUsed { get; set; } public List<string> FullOfferings { get; set; } = new List<string>(); } public class OfferingDef { public int Points { get; set; } public string Kind { get; set; } = "item"; } public class OfferingsTable { public int Version { get; set; } public int RepeatBossPoints { get; set; } = 1; public int DefaultTrophyPoints { get; set; } public Dictionary<string, OfferingDef> Items { get; set; } = new Dictionary<string, OfferingDef>(); } public class BlessingDef { public string Id { get; set; } = ""; public string Name { get; set; } = ""; public string Key { get; set; } = ""; public float Multiplier { get; set; } public float Minutes { get; set; } = 60f; public string Effect { get; set; } = ""; } public class BlessingsTable { public List<string> MilestoneOrder { get; set; } = new List<string>(); public List<string> RandomPool { get; set; } = new List<string>(); public List<BlessingDef> Blessings { get; set; } = new List<BlessingDef>(); } internal static class Tables { public class Message { public string Text; public bool Error; } private class DefaultItem { public string Prefab; public string Kind; public string Section; public int Points; } public const int OfferingsVersion = 2; public static OfferingsTable Offerings = DefaultOfferings(); public static BlessingsTable Blessings = DefaultBlessings(); private static DateTime _offLoaded; private static DateTime _offSeen; private static DateTime _blessLoaded; private static DateTime _blessSeen; private static readonly string[] Kinds = new string[4] { "boss", "lord", "rare", "item" }; private const string BlessingsHeader = "# Blot blessings. key = a world modifier (GlobalKeys). multiplier 0 = on/off modifier.\n# minutes count only while someone is online. milestoneOrder repeats; @random = one of randomPool.\n# Live: save and the server uses it within a few seconds. Only the server's copy counts.\n"; private const string SecBoss = "Bosses, by biome - full value the first time an account offers one"; private const string SecLord = "Biome Lords (the BiomeLords mod) - half the boss of their biome"; private const string SecRare = "Rare trophies and items - every copy"; private const string SecTrophy = "Creature trophies - every copy"; private const string SecOwn = "Your own additions"; private static readonly string[] Sections = new string[4] { "Bosses, by biome - full value the first time an account offers one", "Biome Lords (the BiomeLords mod) - half the boss of their biome", "Rare trophies and items - every copy", "Creature trophies - every copy" }; private static List<DefaultItem> _defaults; private static readonly Regex PlainKey = new Regex("^[A-Za-z0-9_][A-Za-z0-9_.()-]*$"); public static string Dir => Path.Combine(Paths.ConfigPath, "Blot"); public static string OfferingsPath => Path.Combine(Dir, "offerings.yaml"); public static string BlessingsPath => Path.Combine(Dir, "blessings.yaml"); public static List<Message> LoadAll() { return new List<Message> { ReloadOfferings(), ReloadBlessings() }; } public static List<Message> CheckForChanges() { List<Message> list = null; DateTime dateTime = WriteTime(OfferingsPath); if (dateTime != _offLoaded) { if (dateTime == _offSeen) { List<Message> obj = list ?? new List<Message>(); list = obj; obj.Add(ReloadOfferings()); } else { _offSeen = dateTime; } } DateTime dateTime2 = WriteTime(BlessingsPath); if (dateTime2 != _blessLoaded) { if (dateTime2 == _blessSeen) { List<Message> obj2 = list ?? new List<Message>(); list = obj2; obj2.Add(ReloadBlessings()); } else { _blessSeen = dateTime2; } } return list; } public static Message ReloadOfferings() { string offeringsPath = OfferingsPath; try { if (!File.Exists(offeringsPath)) { Offerings = DefaultOfferings(); YamlIO.SaveText(offeringsPath, RenderOfferings(Offerings)); return Ok($"offerings.yaml written with the default list ({Offerings.Items.Count} items)."); } if (!YamlIO.TryLoad<OfferingsTable>(offeringsPath, out var value, out var error)) { return Fail($"offerings.yaml has a mistake and was NOT loaded - the previous list ({Offerings.Items.Count} items) stays in use. {error}"); } List<string> list = new List<string>(); bool flag = false; if (value.Version < 2) { int num = 0; foreach (KeyValuePair<string, OfferingDef> item in DefaultOfferings().Items) { if (!value.Items.ContainsKey(item.Key)) { value.Items[item.Key] = item.Value; num++; } } if (value.DefaultTrophyPoints == 1) { value.DefaultTrophyPoints = 0; } value.Version = 2; list.Add($"upgraded to the full list (+{num} items)"); flag = true; } Normalize(value, list); Offerings = value; if (flag) { YamlIO.SaveText(offeringsPath, RenderOfferings(value)); } return Ok($"offerings.yaml loaded: {value.Items.Count} items" + ((list.Count > 0) ? (" (" + string.Join("; ", list) + ")") : "") + "."); } finally { _offLoaded = (_offSeen = WriteTime(offeringsPath)); } } private static void Normalize(OfferingsTable t, List<string> notes) { if (t.Items == null) { t.Items = new Dictionary<string, OfferingDef>(); } t.RepeatBossPoints = Math.Max(0, t.RepeatBossPoints); t.DefaultTrophyPoints = Math.Max(0, t.DefaultTrophyPoints); Dictionary<string, OfferingDef> dictionary = new Dictionary<string, OfferingDef>(); foreach (KeyValuePair<string, OfferingDef> item in t.Items) { string text = (item.Key ?? "").Trim(); if (text.Length == 0) { continue; } OfferingDef value = item.Value; if (value == null) { notes.Add(text + " has no points - skipped"); continue; } value.Kind = (value.Kind ?? "").Trim().ToLowerInvariant(); if (Array.IndexOf(Kinds, value.Kind) < 0) { notes.Add(text + ": unknown kind '" + value.Kind + "', using item"); value.Kind = "item"; } value.Points = Math.Max(0, value.Points); dictionary[text] = value; } t.Items = dictionary; } public static Message ReloadBlessings() { string blessingsPath = BlessingsPath; try { if (!File.Exists(blessingsPath)) { Blessings = DefaultBlessings(); YamlIO.Save(blessingsPath, Blessings, "# Blot blessings. key = a world modifier (GlobalKeys). multiplier 0 = on/off modifier.\n# minutes count only while someone is online. milestoneOrder repeats; @random = one of randomPool.\n# Live: save and the server uses it within a few seconds. Only the server's copy counts.\n"); return Ok($"blessings.yaml written with the default blessings ({Blessings.Blessings.Count})."); } if (!YamlIO.TryLoad<BlessingsTable>(blessingsPath, out var value, out var error)) { return Fail("blessings.yaml has a mistake and was NOT loaded - the previous blessings stay in use. " + error); } if (value.Blessings == null || value.Blessings.Count == 0) { return Fail("blessings.yaml lists no blessings and was NOT loaded - the previous blessings stay in use."); } if (value.MilestoneOrder == null) { value.MilestoneOrder = new List<string>(); } if (value.RandomPool == null) { value.RandomPool = new List<string>(); } Blessings = value; return Ok($"blessings.yaml loaded: {value.Blessings.Count} blessings."); } finally { _blessLoaded = (_blessSeen = WriteTime(blessingsPath)); } } public static BlessingDef FindBlessing(string idOrName) { string text = Squash(idOrName); if (text.Length == 0) { return null; } foreach (BlessingDef blessing in Blessings.Blessings) { if (Squash(blessing.Id) == text || Squash(blessing.Name) == text) { return blessing; } } BlessingDef blessingDef = null; foreach (BlessingDef blessing2 in Blessings.Blessings) { if (Squash(blessing2.Id).StartsWith(text) || Squash(blessing2.Name).StartsWith(text)) { if (blessingDef != null) { return null; } blessingDef = blessing2; } } return blessingDef; } private static string Squash(string s) { if (string.IsNullOrEmpty(s)) { return ""; } string[] array = s.ToLowerInvariant().Replace("'", "").Split(new char[3] { ' ', '_', '-' }, StringSplitOptions.RemoveEmptyEntries); StringBuilder stringBuilder = new StringBuilder(); string[] array2 = array; foreach (string text in array2) { if (text != "the" && text != "of") { stringBuilder.Append(text); } } return stringBuilder.ToString(); } private static DateTime WriteTime(string path) { try { return File.Exists(path) ? File.GetLastWriteTimeUtc(path) : DateTime.MinValue; } catch { return DateTime.MinValue; } } private static Message Ok(string text) { return new Message { Text = text }; } private static Message Fail(string text) { return new Message { Text = text, Error = true }; } private static List<DefaultItem> DefaultItems() { if (_defaults != null) { return _defaults; } List<DefaultItem> l = new List<DefaultItem>(); Add("Bosses, by biome - full value the first time an account offers one", "TrophyEikthyr", 10, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophyTheElder", 20, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophyBonemass", 35, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophyDragonQueen", 50, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophyGoblinKing", 70, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophySeekerQueen", 95, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "TrophyFader", 125, "boss"); Add("Bosses, by biome - full value the first time an account offers one", "CrownJewel", 160, "boss"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyNeckLord", 5, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyGreydwarfShamanLord", 10, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyDraugrEliteLord", 18, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyFenringLord", 25, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyLoxLord", 35, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophySeekerLord", 48, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyFallerValkyrieLord", 63, "lord"); Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyGammeltrollLord", 80, "lord"); string[] array = new string[6] { "TrophySkeletonHildir", "TrophyCultist_Hildir", "TrophyGoblinBruteBrosBrute", "TrophyGoblinBruteBrosShaman", "TrophyJotunWarrior", "TrophyJotunWitch" }; foreach (string prefab in array) { Add("Rare trophies and items - every copy", prefab, 5, "rare"); } Add("Rare trophies and items - every copy", "DragonEgg", 15, "item"); array = new string[59] { "TrophyAbomination", "TrophyAsksvin", "TrophyBarka", "TrophyBjorn", "TrophyBjornUndead", "TrophyBlob", "TrophyBlob_Frost", "TrophyBlob_Lava", "TrophyBlob_Morkhalla", "TrophyBoar", "TrophyBonemawSerpent", "TrophyCharredArcher", "TrophyCharredMage", "TrophyCharredMelee", "TrophyCultist", "TrophyDeathsquito", "TrophyDeer", "TrophyDeerWhite", "TrophyDraugr", "TrophyDraugrElite", "TrophyDraugrFem", "TrophyDvergr", "TrophyElaking", "TrophyFallenValkyrie", "TrophyFenring", "TrophyForestTroll", "TrophyFrostTroll", "TrophyGhost", "TrophyGjall", "TrophyGoblin", "TrophyGoblinBrute", "TrophyGoblinShaman", "TrophyGreydwarf", "TrophyGreydwarfBrute", "TrophyGreydwarfShaman", "TrophyGrowth", "TrophyHare", "TrophyHatchling", "TrophyKvastur", "TrophyLeech", "TrophyLox", "TrophyMole", "TrophyMoose", "TrophyMorgen", "TrophyNeck", "TrophySGolem", "TrophySeal", "TrophySeeker", "TrophySeekerBrute", "TrophySerpent", "TrophySkeleton", "TrophySkeletonPoison", "TrophySurtling", "TrophyTick", "TrophyUlv", "TrophyVolture", "TrophyWolf", "TrophyWraith", "TrophyWrithan" }; foreach (string prefab2 in array) { Add("Creature trophies - every copy", prefab2, 1, "item"); } return _defaults = l; void Add(string section, string prefab3, int points, string kind) { l.Add(new DefaultItem { Section = section, Prefab = prefab3, Points = points, Kind = kind }); } } private static OfferingsTable DefaultOfferings() { OfferingsTable offeringsTable = new OfferingsTable { Version = 2, RepeatBossPoints = 1, DefaultTrophyPoints = 0 }; foreach (DefaultItem item in DefaultItems()) { offeringsTable.Items[item.Prefab] = new OfferingDef { Points = item.Points, Kind = item.Kind }; } return offeringsTable; } private static string RenderOfferings(OfferingsTable t) { StringBuilder sb = new StringBuilder(); sb.Append("# Blot offerings - everything that scores at the Obliterator, and how many points.\n#\n# LIVE: save this file and the server uses it within a few seconds - no restart, no command.\n# If the file has a mistake, the server keeps the last good list and says why in its log\n# (BepInEx/LogOutput.log), in `blot log` and on the admin Discord channel.\n#\n# Only the SERVER's copy counts. On a dedicated server, players' own copies are ignored;\n# a player's copy is only used for worlds that player hosts.\n#\n# One line per item: PrefabName: { points: N, kind: K }\n# kind: boss | lord full value the first time an account offers it, repeatBossPoints after that\n# rare | item the points every time\n# Add a line to make an item an offering. Delete its line (or set points: 0) to stop it scoring.\n# PrefabName is the game's internal name (TrophyBoar, not \"Boar Trophy\").\n# Anything not listed scores 0. Delete this file to get the default list back.\n\nversion: " + 2 + "\n\n# Points for a boss or Lord offering after the account's first one.\nrepeatBossPoints: " + t.RepeatBossPoints + "\n\n# Points for an UNLISTED item whose prefab name starts with \"Trophy\" (from other mods).\n# 0 = only the items listed below score.\ndefaultTrophyPoints: " + t.DefaultTrophyPoints + "\n\nitems:\n"); HashSet<string> known = new HashSet<string>(from d in DefaultItems() select d.Prefab); int width = Math.Max(28, t.Items.Keys.Select((string k) => Key(k).Length + 1).DefaultIfEmpty(0).Max()); bool first = true; string[] sections = Sections; foreach (string s in sections) { Section(s, from d in DefaultItems() where d.Section == s select d.Prefab); } Section("Your own additions", t.Items.Keys.Where((string k) => !known.Contains(k)).OrderBy<string, string>((string k) => k, StringComparer.Ordinal)); if (t.Items.Count == 0) { sb.Append(" {}\n"); } return sb.ToString(); void Section(string title, IEnumerable<string> prefabs) { List<string> list = prefabs.Where(t.Items.ContainsKey).ToList(); if (list.Count == 0) { return; } if (!first) { sb.Append("\n"); } first = false; sb.Append(" # ").Append(title).Append("\n"); foreach (string item in list) { OfferingDef offeringDef = t.Items[item]; sb.Append(" ").Append((Key(item) + ":").PadRight(width)).Append(" { points: ") .Append(offeringDef.Points.ToString().PadLeft(3)) .Append(", kind: ") .Append(offeringDef.Kind) .Append(" }\n"); } } } private static string Key(string prefab) { if (!PlainKey.IsMatch(prefab)) { return "'" + prefab.Replace("'", "''") + "'"; } return prefab; } private static BlessingsTable DefaultBlessings() { BlessingsTable t = new BlessingsTable { MilestoneOrder = new List<string> { "odins_insight", "freyrs_bounty", "@random" }, RandomPool = new List<string> { "bifrost_hour", "valkyries_grace", "mercy_of_hel", "pack_mule", "feast_of_valhalla", "truce_of_the_gods" } }; Add("odins_insight", "Odin's Insight", "SkillGainRate", 1.5f, 120f, "XP x1.5"); Add("freyrs_bounty", "Freyr's Bounty", "ResourceRate", 1.5f, 120f, "Drops x1.5"); Add("bifrost_hour", "Bifrost Hour", "TeleportAll", 0f, 30f, "Ore and metal pass through portals"); Add("valkyries_grace", "Valkyrie's Grace", "DeathKeepEquip", 0f, 60f, "Keep equipped gear on death"); Add("mercy_of_hel", "Mercy of Hel", "SkillReductionRate", 0.5f, 120f, "Death skill loss x0.5"); Add("pack_mule", "Pack-Mule", "CarryWeightRate", 1.5f, 60f, "Carry weight x1.5"); Add("feast_of_valhalla", "Feast of Valhalla", "FoodRate", 0.67f, 120f, "Food lasts x1.5"); Add("truce_of_the_gods", "Truce of the Gods", "PassiveMobs", 0f, 20f, "Monsters do not attack"); return t; void Add(string id, string name, string key, float mult, float minutes, string effect) { t.Blessings.Add(new BlessingDef { Id = id, Name = name, Key = key, Multiplier = mult, Minutes = minutes, Effect = effect }); } } } internal static class Discord { public static void Public(string text) { Post("public", text); } public static void Admin(string text) { Post("admin", text); } public static void Post(string channel, string text) { if (string.IsNullOrWhiteSpace(text)) { return; } if (text.Length > 1900) { text = text.Substring(0, 1900) + "..."; } BlotPlugin.Log.LogInfo((object)("[discord:" + channel + "] " + text)); if (BlotConfig.PreferServerGuard.Value && ServerGuardBridge.PostDiscord(channel, text)) { return; } string url = ((channel == "admin" && !string.IsNullOrWhiteSpace(BlotConfig.DiscordWebhookUrlAdmin.Value)) ? BlotConfig.DiscordWebhookUrlAdmin.Value : BlotConfig.DiscordWebhookUrl.Value); if (string.IsNullOrWhiteSpace(url)) { return; } string body = "{\"content\":" + JsonString(text) + "}"; ThreadPool.QueueUserWorkItem(delegate { try { HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url.Trim()); httpWebRequest.Method = "POST"; httpWebRequest.ContentType = "application/json"; httpWebRequest.Timeout = 10000; byte[] bytes = Encoding.UTF8.GetBytes(body); using (Stream stream = httpWebRequest.GetRequestStream()) { stream.Write(bytes, 0, bytes.Length); } using ((HttpWebResponse)httpWebRequest.GetResponse()) { } } catch (Exception ex) { BlotPlugin.Log.LogWarning((object)("Discord post failed: " + ex.Message)); } }); } private static string JsonString(string s) { StringBuilder stringBuilder = new StringBuilder("\""); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\t': stringBuilder.Append("\\t"); continue; case '\r': continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.Append('"').ToString(); } } internal static class ServerGuardBridge { private static bool _probed; private static Type _api; private static PropertyInfo _progressionActive; private static MethodInfo _postDiscord; private static MethodInfo _isStaff; public static bool Present { get { Probe(); return _api != null; } } public static bool ProgressionActive { get { Probe(); try { return _progressionActive != null && (bool)_progressionActive.GetValue(null, null); } catch { return false; } } } private static void Probe() { if (_probed) { return; } _probed = true; try { _api = Type.GetType("ValheimServerGuard.ServerGuardApi, Valheim-ServerGuard"); if (!(_api == null)) { FieldInfo field = _api.GetField("ApiVersion"); if (field == null || (int)field.GetValue(null) < 1) { _api = null; return; } _progressionActive = _api.GetProperty("ProgressionActive"); _postDiscord = _api.GetMethod("PostDiscord"); _isStaff = _api.GetMethod("IsStaff"); BlotPlugin.Log.LogInfo((object)"ServerGuard API found: Discord posts and staff checks go through ServerGuard."); } } catch (Exception ex) { BlotPlugin.Log.LogWarning((object)("ServerGuard API probe failed: " + ex.Message)); _api = null; } } public static bool PostDiscord(string channel, string message) { Probe(); try { return _postDiscord != null && (bool)_postDiscord.Invoke(null, new object[2] { channel, message }); } catch { return false; } } public static bool IsStaff(string steamId) { Probe(); try { return _isStaff != null && (bool)_isStaff.Invoke(null, new object[1] { steamId }); } catch { return false; } } } public enum Rite { Restock, Regrowth } internal static class BlotServer { [HarmonyPatch(typeof(ZoneSystem), "GenerateLocationsIfNeeded")] private static class Patch_GenerateLocationsIfNeeded { private static void Prefix() { try { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { OnWorldLoaded(); } } catch (Exception arg) { BlotPlugin.Log.LogError((object)$"Blot world load failed: {arg}"); } } } [HarmonyPatch(typeof(ZNet), "Shutdown")] private static class Patch_ZNet_Shutdown { private static void Prefix() { try { OnShutdown(); } catch (Exception arg) { BlotPlugin.Log.LogError((object)$"Blot shutdown save failed: {arg}"); } } } [HarmonyPatch(typeof(ZNet), "RPC_CharacterID")] private static class Patch_ZNet_CharacterID { private static void Postfix(ZRpc rpc) { try { if (Active && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZNetPeer peer = ZNet.instance.GetPeer(rpc); if (peer != null) { SendStateTo(Accounts.FromPeer(peer)); } } } catch (Exception ex) { BlotPlugin.Log.LogWarning((object)("Peer ready sync failed: " + ex.Message)); } } } [HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")] private static class Patch_RPC_SetGlobalKey { private static void Postfix() { try { if (Active) { CheckRitesReady(); MarkDirty(state: true); } } catch (Exception ex) { BlotPlugin.Log.LogWarning((object)("Rite readiness check failed: " + ex.Message)); } } } private enum PlaceResult { Refilled, Built, Near, Skipped, Unknown } public class ScoreLine { public string Prefab; public int Stack; public int Points; public string Why = ""; } private class RiteVote { public int Id; public Rite Rite; public string ProposerAccount = ""; public string ProposerName = ""; public double SecondsLeft; public readonly Dictionary<string, bool> Votes = new Dictionary<string, bool>(); public readonly Dictionary<string, bool?> Fake = new Dictionary<string, bool?>(); } public static readonly Rite[] AllRites = new Rite[2] { Rite.Restock, Rite.Regrowth }; public static BlotState State = new BlotState(); public static ContributionsDoc Contrib = new ContributionsDoc(); private static bool _dirty; private static bool _stateDirty; private static float _secondTimer; private static float _saveTimer; private static float _broadcastTimer; private static readonly string[] CombatKeys = new string[4] { "playerdamage", "enemydamage", "enemyspeedsize", "enemyleveluprate" }; private static int _busyJobs; private static readonly object EnterWaiting = new object(); private static readonly int PlayerHash = StringExtensionMethods.GetStableHashCode("Player"); private static readonly Dictionary<int, HashSet<int>> LocationPrefabSets = new Dictionary<int, HashSet<int>>(); private static int _restockTwins; private static RiteVote _vote; private static int _nextVoteId = 1; private static readonly Dictionary<string, DateTime> _voteCooldown = new Dictionary<string, DateTime>(); public static bool Active { get; private set; } public static long WorldUid { get; private set; } public static string DataDir { get; private set; } = ""; public static bool JobRunning => _busyJobs > 0; public static string JobStatus { get; private set; } = ""; private static string P(string file) { return Path.Combine(DataDir, file); } public static string DirFor(long worldUid) { return Path.Combine(Paths.ConfigPath, "Blot", worldUid.ToString(CultureInfo.InvariantCulture)); } private static void OnWorldLoaded() { World world = ZNet.World; if (world == null) { return; } WorldUid = world.m_uid; DataDir = DirFor(WorldUid); Directory.CreateDirectory(DataDir); State = YamlIO.Load<BlotState>(P("state.yaml")); Contrib = YamlIO.Load<ContributionsDoc>(P("contributions.yaml")); State.RitesAnnouncedReady.RemoveAll((string n) => !TryParseRite(n, out var _)); Active = true; foreach (Tables.Message item in Tables.LoadAll()) { ReportTables(item); } RollBackLegacyRewards(); RestoreBlessingsAtBoot(); BlotPlugin.Log.LogInfo((object)$"Blot server active: world {world.m_name} ({WorldUid}), pool {State.Pool}, offered {State.Offered}."); MarkDirty(state: true); } private static void OnShutdown() { if (Active) { SaveAll(); Active = false; ClearVote(); State = new BlotState(); Contrib = new ContributionsDoc(); DataDir = ""; WorldUid = 0L; } } public static void ReportTables(Tables.Message m) { if (m != null) { if (m.Error) { BlotPlugin.Log.LogError((object)m.Text); AddLog(m.Text); Discord.Admin(":warning: " + m.Text); } else { BlotPlugin.Log.LogInfo((object)m.Text); } } } public static void SaveAll() { if (Active && !string.IsNullOrEmpty(DataDir)) { YamlIO.Save(P("state.yaml"), State); YamlIO.Save(P("contributions.yaml"), Contrib); _dirty = false; } } public static void MarkDirty(bool state = false) { _dirty = true; if (state) { _stateDirty = true; } } public static void AddLog(string line) { State.Log.Add($"{DateTime.UtcNow:yyyy-MM-dd HH:mm} {line}"); if (State.Log.Count > 200) { State.Log.RemoveRange(0, State.Log.Count - 200); } _dirty = true; } public static void Tick() { if (!Active) { return; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { Active = false; return; } float unscaledDeltaTime = Time.unscaledDeltaTime; _secondTimer += unscaledDeltaTime; _saveTimer += unscaledDeltaTime; _broadcastTimer += unscaledDeltaTime; if (_secondTimer >= 1f) { double secs = _secondTimer; _secondTimer = 0f; TickBlessings(secs); TickVote(secs); TickJobs(); List<Tables.Message> list = Tables.CheckForChanges(); if (list != null) { foreach (Tables.Message item in list) { ReportTables(item); if (!item.Error) { AddLog(item.Text); } } MarkDirty(state: true); } } if (_saveTimer >= 5f) { _saveTimer = 0f; if (_dirty) { SaveAll(); } } if ((_stateDirty && _broadcastTimer >= 1f) || _broadcastTimer >= 10f) { _broadcastTimer = 0f; _stateDirty = false; BroadcastState(); } } public static int Cost(Rite r) { if (r != Rite.Restock) { return BlotConfig.RegrowthCost.Value; } return BlotConfig.RestockCost.Value; } public static string GateKey(Rite r) { if (r != Rite.Restock) { return BlotConfig.RegrowthGate.Value; } return BlotConfig.RestockGate.Value; } public static bool GateOpen(Rite r) { string text = (GateKey(r) ?? "").Trim(); if (text.Length != 0) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(text); } return false; } return true; } public static string RiteName(Rite r) { return r.ToString(); } public static bool TryParseRite(string s, out Rite rite) { rite = Rite.Restock; switch ((s ?? "").Trim().ToLowerInvariant()) { case "restock": case "1": case "i": rite = Rite.Restock; return true; case "regrowth": case "2": case "ii": rite = Rite.Regrowth; return true; default: return false; } } public static AccountContribution GetContribution(string account, string name, bool create) { if (string.IsNullOrEmpty(account)) { return null; } if (!Contrib.Accounts.TryGetValue(account, out var value)) { if (!create) { return null; } value = new AccountContribution(); Contrib.Accounts[account] = value; } if (!string.IsNullOrEmpty(name)) { value.Name = name; } return value; } public static int ContributionOf(string account) { return GetContribution(account, null, create: false)?.Total ?? 0; } public static string RiteBlocker(Rite r, string proposerAccount, bool ignoreCost) { if (JobRunning) { return "a rite is still running"; } if (!ignoreCost && State.Pool < Cost(r)) { return $"the pool holds {State.Pool}/{Cost(r)}"; } if (!GateOpen(r)) { return "its boss has not fallen (" + GateKey(r) + ")"; } if (proposerAccount != null && ContributionOf(proposerAccount) < BlotConfig.RiteMinContribution.Value) { return $"you must offer at least {BlotConfig.RiteMinContribution.Value} points first"; } return null; } public static void BroadcastState() { foreach (Who item in Accounts.Online()) { SendStateTo(item); } } public static void SendStateTo(Who w) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown if (w == null) { return; } ZPackage val = new ZPackage(); val.Write(4); val.Write(State.Pool); val.Write(State.Offered); val.Write(BlotConfig.MilestoneEvery.Value); val.Write(AllRites.Length); Rite[] allRites = AllRites; foreach (Rite r in allRites) { val.Write(RiteName(r)); val.Write(Cost(r)); val.Write(GateOpen(r)); val.Write(RiteBlocker(r, w.Account, ignoreCost: false) ?? ""); } val.Write(State.Blessings.Count); foreach (ActiveBlessing blessing in State.Blessings) { BlessingDef blessingDef = Tables.FindBlessing(blessing.Id); val.Write((blessingDef != null) ? blessingDef.Name : blessing.Id); val.Write((blessingDef != null) ? blessingDef.Effect : ""); val.Write((float)blessing.SecondsLeft); } WriteVote(val, w.Account); AccountContribution contribution = GetContribution(w.Account, null, create: false); val.Write(contribution?.Total ?? 0); val.Write(BlotConfig.DailyCapEnabled.Value ? BlotConfig.DailyCapPoints.Value : 0); val.Write((contribution != null && contribution.CapDay == CapDay()) ? contribution.CapUsed : 0); Net.ToClient(w.Uid, "Blot_State", val); } public static void Announce(string text) { Net.NoticeTo(0L, text); } private static void CheckMilestones(int before, int after) { int num = Math.Max(1, BlotConfig.MilestoneEvery.Value); int num2 = after / num; while (State.MilestonesGranted < num2) { State.MilestonesGranted++; BlessingDef blessingDef = MilestoneBlessing(State.MilestonesGranted); if (blessingDef != null) { StartBlessing(blessingDef, blessingDef.Minutes, $"milestone {State.MilestonesGranted * num}"); } } } public static BlessingDef MilestoneBlessing(int milestone) { List<string> milestoneOrder = Tables.Blessings.MilestoneOrder; if (milestoneOrder == null || milestoneOrder.Count == 0) { return null; } string text = milestoneOrder[(milestone - 1) % milestoneOrder.Count]; if (text == "@random") { List<BlessingDef> list = (from b in Tables.Blessings.RandomPool.Select(Tables.FindBlessing) where b != null select b).ToList(); if (list.Count == 0) { return null; } return list[Random.Range(0, list.Count)]; } return Tables.FindBlessing(text); } public unsafe static string StartBlessing(BlessingDef def, float minutes, string why) { //IL_0122: 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_01c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null) { return "The world is not ready."; } if (!TryParseKey(def.Key, out var key)) { return "`" + def.Key + "` is not a world modifier."; } string key2 = ((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(); ActiveBlessing activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing b) => b.Key == key2); if (activeBlessing != null) { if (activeBlessing.Id != def.Id) { return $"Another blessing ({activeBlessing.Id}) already holds {key}."; } activeBlessing.SecondsLeft += (double)minutes * 60.0; AnnounceBlessingStart(def, activeBlessing.SecondsLeft, why, extended: true); MarkDirty(state: true); return def.Name + " extended to " + Format(activeBlessing.SecondsLeft) + "."; } ActiveBlessing activeBlessing2 = new ActiveBlessing { Id = def.Id, Key = key2, SecondsLeft = (double)minutes * 60.0 }; string text = default(string); if (ZoneSystem.instance.GetGlobalKey(key, ref text)) { activeBlessing2.HadOriginal = true; activeBlessing2.Original = text ?? ""; } if (def.Multiplier > 0f) { float num = 100f; if (activeBlessing2.HadOriginal && float.TryParse(activeBlessing2.Original, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { num = result; } ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString() + " " + Math.Round(num * def.Multiplier, 1).ToString(CultureInfo.InvariantCulture)); } else { if (activeBlessing2.HadOriginal) { return $"{key} is already on in this world - nothing to bless."; } ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString()); } State.Blessings.Add(activeBlessing2); AddLog("blessing " + def.Id + " started (" + why + ")"); AnnounceBlessingStart(def, activeBlessing2.SecondsLeft, why, extended: false); MarkDirty(state: true); return def.Name + " started for " + Format(activeBlessing2.SecondsLeft) + "."; } public static string EndBlessing(string idOrKey, string why) { ActiveBlessing activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing x) => x.Id == idOrKey || x.Key == idOrKey); if (activeBlessing == null) { BlessingDef def0 = Tables.FindBlessing(idOrKey); if (def0 != null) { activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing x) => x.Id == def0.Id); } } if (activeBlessing == null) { return "No blessing `" + idOrKey + "` is running."; } Restore(activeBlessing); State.Blessings.Remove(activeBlessing); BlessingDef blessingDef = Tables.FindBlessing(activeBlessing.Id); string text = ((blessingDef != null) ? blessingDef.Name : activeBlessing.Id); AddLog("blessing " + activeBlessing.Id + " ended (" + why + ")"); Announce("Blot Blessing - " + text + " has faded."); Discord.Public(":hourglass: **" + text + "** has faded."); MarkDirty(state: true); return text + " ended."; } private unsafe static void Restore(ActiveBlessing b) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null || !TryParseKey(b.Key, out var key)) { return; } if (b.HadOriginal) { if (b.Original.Length > 0) { ServerSetKey($"{key} {b.Original}"); } else { ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString()); } } else { ServerRemoveKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString()); } } private static void TickBlessings(double secs) { if (State.Blessings.Count == 0 || (BlotConfig.OnlineOnlyTimers.Value && Accounts.Online().Count == 0)) { return; } foreach (ActiveBlessing item in State.Blessings.ToList()) { item.SecondsLeft -= secs; if (item.SecondsLeft <= 0.0) { EndBlessing(item.Id, "time"); } } _dirty = true; } private unsafe static void RestoreBlessingsAtBoot() { foreach (ActiveBlessing item in State.Blessings.ToList()) { if (item.SecondsLeft <= 0.0) { Restore(item); State.Blessings.Remove(item); continue; } BlessingDef blessingDef = Tables.FindBlessing(item.Id); if (blessingDef == null || !TryParseKey(item.Key, out var key)) { Restore(item); State.Blessings.Remove(item); } else if (blessingDef.Multiplier > 0f) { float num = 100f; if (item.HadOriginal && float.TryParse(item.Original, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { num = result; } ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString() + " " + Math.Round(num * blessingDef.Multiplier, 1).ToString(CultureInfo.InvariantCulture)); } else { ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString()); } } } private static void RollBackLegacyRewards() { if ((Object)(object)ZoneSystem.instance == (Object)null) { return; } if (State.Permanent != null) { Restore(State.Permanent); AddLog("0.2.x permanent blessing " + State.Permanent.Id + " rolled back"); BlotPlugin.Log.LogInfo((object)("Rolled back the 0.2.x permanent blessing " + State.Permanent.Id + ".")); State.Permanent = null; } if (string.IsNullOrEmpty(State.Difficulty) && State.DifficultyOriginal == null) { return; } string[] combatKeys = CombatKeys; for (int i = 0; i < combatKeys.Length; i++) { ServerRemoveKey(combatKeys[i]); } foreach (string item in State.DifficultyOriginal ?? new List<string>()) { ServerSetKey(item); } AddLog("0.2.x difficulty " + State.Difficulty + " rolled back to the server's own combat setting"); BlotPlugin.Log.LogInfo((object)("Rolled back the 0.2.x difficulty '" + State.Difficulty + "' to the server's own combat setting.")); State.Difficulty = ""; State.DifficultyOriginal = null; } private static void AnnounceBlessingStart(BlessingDef def, double seconds, string why, bool extended) { string text = Format(seconds); Announce(extended ? ("Blot Blessing - " + def.Name + " lengthened: " + def.Effect + " for " + text + " of play.") : ("Blot Blessing - " + def.Name + ": " + def.Effect + " for " + text + " of play.")); Discord.Public(extended ? (":sparkles: **" + def.Name + "** grows longer - " + def.Effect + ", now " + text + " of play.") : $":sparkles: **{def.Name}** is upon the realm - {def.Effect} for {text} of play. Offered by the realm so far: {State.Offered}."); } public static void ServerSetKey(string line) { ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return; } string item = line.ToLowerInvariant(); if (!instance.m_globalKeys.Contains(item)) { instance.GlobalKeyAdd(line, true); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeers().Count > 0) { instance.SendGlobalKeys(0L); } CheckRitesReady(); } } public static void ServerRemoveKey(string name) { ZoneSystem instance = ZoneSystem.instance; if (!((Object)(object)instance == (Object)null) && instance.GlobalKeyRemove(name.ToLowerInvariant(), true) && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeers().Count > 0) { instance.SendGlobalKeys(0L); } } private static bool TryParseKey(string s, out GlobalKeys key) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected I4, but got Unknown key = (GlobalKeys)41; if (string.IsNullOrEmpty(s)) { return false; } foreach (GlobalKeys value in Enum.GetValues(typeof(GlobalKeys))) { if ((int)value >= 41) { break; } if (string.Equals(((object)value/*cast due to .constrained prefix*/).ToString(), s.Trim(), StringComparison.OrdinalIgnoreCase)) { key = (GlobalKeys)(int)value; return true; } } return false; } public static string Format(double seconds) { if (seconds < 0.0) { seconds = 0.0; } TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); if (timeSpan.TotalHours >= 1.0) { return $"{(int)timeSpan.TotalHours} h {timeSpan.Minutes:00} min"; } if (timeSpan.TotalMinutes >= 1.0) { return $"{timeSpan.Minutes} min"; } return $"{timeSpan.Seconds} s"; } public static void OnCommand(long sender, ZPackage pkg) { Who who = Accounts.FromSender(sender); if (who == null) { return; } string text = pkg.ReadString() ?? ""; string text2; if (!Active) { text2 = "Blot is not running on this server yet."; } else { try { text2 = Dispatch(who, text.Trim()); } catch (Exception ex) { BlotPlugin.Log.LogError((object)$"Command '{text}' from {who.Name}: {ex}"); text2 = "Error: " + ex.Message; } } Reply(who, text2); } private static void Reply(Who who, string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(text ?? ""); Net.ToClient(who.Uid, "Blot_Reply", val); } public static bool IsAdmin(Who who) { if (who == null) { return false; } if (who.IsHost) { return true; } try { if (ZNet.instance.IsAdmin(who.Account)) { return true; } } catch { } return ServerGuardBridge.IsStaff(who.Account); } private static string Dispatch(Who who, string text) { List<string> list = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (list.Count > 0 && list[0].Equals("blot", StringComparison.OrdinalIgnoreCase)) { list.RemoveAt(0); } string text2 = ((list.Count > 0) ? list[0].ToLowerInvariant() : "help"); List<string> list2 = list.Skip(1).ToList(); bool flag = IsAdmin(who); switch (text2) { case "help": return Help(flag); case "me": return Me(who); case "status": return Status(flag); case "rite": { if (list2.Count == 0 || !TryParseRite(list2[0], out var rite)) { return "Usage: blot rite restock|regrowth"; } return Propose(who, rite); } case "yes": return CastVote(who, yes: true); case "no": return CastVote(who, yes: false); default: if (!flag) { return "Unknown or admin-only command `" + text2 + "`. Try `blot help`."; } if (text2 != "status" && text2 != "log") { Discord.Admin(":hammer_and_wrench: **" + who.Name + "** ran `blot " + string.Join(" ", list) + "`"); } switch (text2) { case "points": return Points(list2); case "perform": return AdminPerform(who, list2); case "vote": if (list2.Count > 0 && list2[0] == "cancel") { return CancelVote(); } return "Usage: blot vote cancel"; case "bless": return Bless(list2); case "reload": { List<Tables.Message> list3 = Tables.LoadAll(); foreach (Tables.Message item in list3) { if (item.Error) { ReportTables(item); } } MarkDirty(state: true); return string.Join("\n", list3.Select((Tables.Message m) => m.Text)); } case "log": { int result; int num = ((list2.Count > 0 && int.TryParse(list2[0], out result)) ? Mathf.Clamp(result, 1, 40) : 15); if (State.Log.Count != 0) { return string.Join("\n", State.Log.Skip(Math.Max(0, State.Log.Count - num))); } return "The Blot log is empty."; } case "test": return Test(who, list2); default: return "Unknown command `" + text2 + "`. Try `blot help`."; } } } private static string Help(bool admin) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Blot commands (F5 console, or /blot in chat):"); stringBuilder.AppendLine(" blot me your offerings and today's cap"); stringBuilder.AppendLine(" blot status pool, rites, blessings"); stringBuilder.AppendLine(" blot rite restock|regrowth propose a rite (opens a vote)"); stringBuilder.AppendLine(" blot yes | blot no vote on the open proposal"); if (!admin) { return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } stringBuilder.AppendLine("Admin:"); stringBuilder.AppendLine(" blot points add|set <n> change the pool"); stringBuilder.AppendLine(" blot perform <rite> [force] perform now: no vote, no cost (force: no gate either)"); stringBuilder.AppendLine(" blot vote cancel close the open vote as failed"); stringBuilder.AppendLine(" blot bless <blessing> [minutes] | blot bless end <blessing>"); stringBuilder.AppendLine(" blot reload | blot log [n]"); stringBuilder.AppendLine(" blot test ... test helpers - `blot test` lists them"); return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static string Me(Who who) { AccountContribution contribution = GetContribution(who.Account, null, create: false); int value = BlotConfig.DailyCapPoints.Valu