using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Splatform; using Stavebound.Compat; using Stavebound.Config; using Stavebound.Portals; using Stavebound.Staves; using Stavebound.Tiers; using Stavebound.Travel; using Stavebound.UI; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("recognizerHD")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 recognizerHD")] [assembly: AssemblyFileVersion("0.9.0.0")] [assembly: AssemblyInformationalVersion("0.9.0+79f0f11b78b5c276b3ccb4c9bce73cfc2ad3e329")] [assembly: AssemblyProduct("Stavebound")] [assembly: AssemblyTitle("Stavebound")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/recognizerHD/stave-bound")] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("0.9.0.0")] namespace Stavebound { internal static class BuildInfo { internal const string Guid = "com.recognizerhd.stavebound"; internal const string Name = "Stavebound"; internal const string Version = "0.9.0"; } [BepInPlugin("com.recognizerhd.stavebound", "Stavebound", "0.9.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal sealed class Plugin : BaseUnityPlugin { private Harmony _harmony; internal static Plugin Instance { get; private set; } private void Awake() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown Instance = this; StaveboundConfig.Bind(((BaseUnityPlugin)this).Config); SelectorKeys.Bind(((BaseUnityPlugin)this).Config); Translations.Add(); StavePieces.Register(); PortalRegistry.Register(); _harmony = new Harmony("com.recognizerhd.stavebound"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Logger.LogInfo((object)"Stavebound 0.9.0 loaded."); } private void Start() { ConflictDetector.WarnAboutKnownConflicts(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; Instance = null; } } } namespace Stavebound.UI { internal static class DestinationSelector { private enum SortOrder { Distance, Name } private static readonly List Candidates = new List(); private static readonly List Pins = new List(); private const int VisibleRows = 7; private static ZDOID _sourceId; private static long _sourcePid; private static Vector3 _sourcePosition; private static string _sourceName; private static SortOrder _order = SortOrder.Distance; private static bool _onlyWhatAcceptsMyCargo; private static int _highlight; private static Clearance _carrying; private static Clearance _sourceMask; private static GameObject _panel; private static Text _text; private static bool _updateSeen; internal static bool IsOpen { get; private set; } internal static void Open(TeleportWorld portal, Humanoid who) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //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) ZDO val = PortalTarget.ZdoOf(portal); if (val == null || (Object)(object)Minimap.instance == (Object)null) { return; } if (!ReaimGuard.MayReaim(((Component)portal).transform.position, out var refusal)) { if (who != null) { ((Character)who).Message((MessageType)2, refusal, 0, (Sprite)null); } return; } _sourceId = val.m_uid; long pid = PortalTarget.GetPid(val); string text = val.GetString(ZDOVars.s_tag, string.Empty); _sourceName = (string.IsNullOrEmpty(text) ? "this portal" : ("\"" + text + "\"")); _sourcePosition = ((Component)portal).transform.position; _sourcePid = pid; _sourceMask = ClearanceGate.MaskOf(val); _carrying = CarriedTiers((Player)(object)((who is Player) ? who : null)); _onlyWhatAcceptsMyCargo = false; Rebuild(0L); if (Candidates.Count == 0) { if (who != null) { ((Character)who).Message((MessageType)2, Translations.Get("stave_sel_nowhere"), 0, (Sprite)null); } return; } long current = PortalTarget.GetDestination(val); _highlight = Candidates.FindIndex((PortalRecord p) => p.Pid == current); if (_highlight < 0) { _highlight = 0; } AddPins(); BuildPanel(); IsOpen = true; _updateSeen = false; Minimap.instance.SetMapMode((MapMode)2); ShowHighlight(); Logger.LogInfo((object)$"Selector opened for {_sourceName} with {Candidates.Count} destination(s)."); } internal static void Update() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 if (!IsOpen) { return; } if (!_updateSeen) { _updateSeen = true; Logger.LogInfo((object)"Selector is receiving frames."); } if ((Object)(object)Minimap.instance == (Object)null || (int)Minimap.instance.m_mode != 2) { Close(); return; } if (Cancelled()) { Close(); return; } if (Confirmed()) { Commit(); return; } if (SelectorKeys.Pressed("stave_filter")) { _onlyWhatAcceptsMyCargo = !_onlyWhatAcceptsMyCargo; Rebuild(Held()); ShowHighlight(); return; } if (SelectorKeys.Pressed("stave_sort")) { _order = ((_order == SortOrder.Distance) ? SortOrder.Name : SortOrder.Distance); Rebuild(Held()); ShowHighlight(); return; } int num = Stepped(); if (num != 0) { _highlight = (_highlight + num + Candidates.Count) % Candidates.Count; ShowHighlight(); } } private static long Held() { if (_highlight < 0 || _highlight >= Candidates.Count) { return 0L; } return Candidates[_highlight].Pid; } private static void Rebuild(long keep) { Candidates.Clear(); Candidates.AddRange(PortalRegistry.All.Where((PortalRecord p) => p.Pid != _sourcePid)); if (_onlyWhatAcceptsMyCargo && _carrying != Clearance.None) { Candidates.RemoveAll((PortalRecord p) => !Accepts(p)); } Sort(); _highlight = ((keep != 0L) ? Candidates.FindIndex((PortalRecord p) => p.Pid == keep) : 0); if (_highlight < 0) { _highlight = 0; } } private static void Sort() { if (_order == SortOrder.Name) { Candidates.Sort((PortalRecord a, PortalRecord b) => string.Compare(Describe(a), Describe(b), StringComparison.CurrentCultureIgnoreCase)); } else { Candidates.Sort((PortalRecord a, PortalRecord b) => Vector3.Distance(_sourcePosition, a.Position).CompareTo(Vector3.Distance(_sourcePosition, b.Position))); } } internal static void HighlightNearest(Vector3 worldPoint) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!IsOpen || Candidates.Count == 0) { return; } int highlight = 0; float num = float.MaxValue; for (int i = 0; i < Candidates.Count; i++) { Vector3 val = Candidates[i].Position - worldPoint; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; highlight = i; } } _highlight = highlight; ShowHighlight(); } private static void Commit() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(_sourceId) : null); if (val == null) { Close(); return; } PortalRecord portal = Candidates[_highlight]; PortalTarget.Set(val, portal.Pid); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, Translations.Format("stave_sel_aimed", _sourceName, Describe(portal)), 0, (Sprite)null); } Close(); } internal static void Reset() { Close(); } private static void Close() { IsOpen = false; RemovePins(); if ((Object)(object)_panel != (Object)null) { Object.Destroy((Object)(object)_panel); _panel = null; _text = null; } Candidates.Clear(); if ((Object)(object)Minimap.instance != (Object)null && (Object)(object)Minimap.instance.m_mapLarge != (Object)null && Minimap.instance.m_mapLarge.activeSelf) { Minimap.instance.SetMapMode((MapMode)1); } } private static void AddPins() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) RemovePins(); foreach (PortalRecord candidate in Candidates) { PinData item = Minimap.instance.AddPin(candidate.Position, (PinType)3, Describe(candidate), false, false, 0L, default(PlatformUserID)); Pins.Add(item); } } private static void RemovePins() { foreach (PinData pin in Pins) { if (pin != null) { Minimap instance = Minimap.instance; if (instance != null) { instance.RemovePin(pin); } } } Pins.Clear(); } private static void ShowHighlight() { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_text == (Object)null) { return; } if (Candidates.Count == 0) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Translations.Format("stave_sel_title", _sourceName)); stringBuilder.AppendLine(); stringBuilder.AppendLine("" + Translations.Get("stave_sel_empty") + ""); stringBuilder.AppendLine(); stringBuilder.Append("[" + Bound("stave_filter") + "] " + Translations.Get("stave_sel_show_all") + " [" + Bound("stave_cancel") + "] " + Translations.Get("stave_sel_cancel") + ""); SetPanelText(stringBuilder.ToString()); return; } PortalRecord destination = Candidates[_highlight]; Minimap instance = Minimap.instance; if (instance != null) { instance.ShowPointOnMap(destination.Position); } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.AppendLine(Translations.Format("stave_sel_title", _sourceName)); string text = Translations.Get((_order == SortOrder.Distance) ? "stave_sel_by_distance" : "stave_sel_by_name"); string text2 = (_onlyWhatAcceptsMyCargo ? Translations.Get("stave_sel_filtered") : string.Empty); stringBuilder2.AppendLine("" + text + text2 + ""); string text3 = FlowNote(); if (text3 != null) { stringBuilder2.AppendLine("" + text3 + ""); } stringBuilder2.AppendLine(Verdict(destination)); stringBuilder2.AppendLine(); int num = Mathf.Clamp(_highlight - 3, 0, Mathf.Max(0, Candidates.Count - 7)); int num2 = Mathf.Min(num + 7, Candidates.Count); stringBuilder2.AppendLine((num > 0) ? ("" + Translations.Format("stave_sel_more_above", num) + "") : " "); for (int i = num; i < num2; i++) { PortalRecord portal = Candidates[i]; float num3 = Vector3.Distance(_sourcePosition, portal.Position); string text4 = $"{Describe(portal)} {num3:F0}m\n {Chips(portal)}"; stringBuilder2.AppendLine((i == _highlight) ? ("» " + text4 + "") : (" " + text4 + "")); } stringBuilder2.AppendLine((num2 < Candidates.Count) ? ("" + Translations.Format("stave_sel_more_below", Candidates.Count - num2) + "") : " "); stringBuilder2.AppendLine(); stringBuilder2.AppendLine("[" + Bound("stave_confirm") + "] " + Translations.Get("stave_sel_confirm") + " [" + Bound("stave_cancel") + "] " + Translations.Get("stave_sel_cancel") + ""); stringBuilder2.Append("[" + Bound("stave_previous") + " / " + Bound("stave_next") + "] " + Translations.Get("stave_sel_change") + " [" + Bound("stave_sort") + "] " + Translations.Get("stave_sel_sort") + " [" + Bound("stave_filter") + "] " + Translations.Get("stave_sel_filter") + ""); SetPanelText(stringBuilder2.ToString()); } private static void SetPanelText(string text) { if (text.Length > 2400) { Logger.LogWarning((object)($"Selector text is {text.Length} characters, past the {2400} this panel can " + "safely draw. Expect it to render blank. Shorten a row, or drop VisibleRows.")); } _text.text = text; } private static string Chips(PortalRecord portal) { Clearance clearanceMask = (Clearance)portal.ClearanceMask; StringBuilder stringBuilder = new StringBuilder(""); Clearance[] ladder = ClearanceExtensions.Ladder; foreach (Clearance clearance in ladder) { bool num = (clearanceMask & clearance) == clearance; bool flag = (_carrying & clearance) == clearance; if (num) { stringBuilder.Append(clearance.Symbol()); } else if (flag) { stringBuilder.Append("" + clearance.Symbol() + ""); } else { stringBuilder.Append("··"); } stringBuilder.Append(' '); } return stringBuilder.ToString().TrimEnd(Array.Empty()) + ""; } private static string Verdict(PortalRecord destination) { if (_carrying == Clearance.None) { return "" + Translations.Get("stave_sel_carrying_nothing") + ""; } int num = Candidates.Count(Accepts); string text = "" + Translations.Format("stave_sel_tally", num, Candidates.Count) + ""; if (!Accepts(destination)) { return "" + Translations.Get("stave_sel_refuses") + " " + text; } return "" + Translations.Get("stave_sel_takes") + " " + text; } private static string FlowNote() { return (StaveboundConfig.Flow?.Value ?? MaterialFlow.Both) switch { MaterialFlow.Both => Translations.Get("stave_sel_flow_both"), MaterialFlow.Deliver => Translations.Get("stave_sel_flow_deliver"), _ => null, }; } private static bool Accepts(PortalRecord portal) { return (ClearanceGate.EffectiveMask(_sourceMask, (Clearance)portal.ClearanceMask) & _carrying) == _carrying; } private static Clearance CarriedTiers(Player player) { Clearance clearance = Clearance.None; Inventory val = ((player != null) ? ((Humanoid)player).GetInventory() : null); if (val == null) { return clearance; } foreach (ItemData allItem in val.GetAllItems()) { if (allItem?.m_shared != null && !allItem.m_shared.m_teleportable) { string prefabName = (((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : null); clearance |= TierMap.RequiredFor(prefabName); } } return clearance; } private static string Bound(string button) { return SelectorKeys.KeyLabel(button); } private static string Describe(PortalRecord portal) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) if (StaveboundConfig.HidePortalNames.Value) { return Translations.Format("stave_portal_at", portal.Position.x.ToString("F0"), portal.Position.z.ToString("F0")); } if (!string.IsNullOrEmpty(portal.Name)) { return portal.Name; } return Translations.Get("stave_unnamed_portal"); } private static void BuildPanel() { //IL_0030: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_panel != (Object)null) { Object.Destroy((Object)(object)_panel); } _panel = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(-600f, -60f), 380f, 540f); _text = GUIManager.Instance.CreateText(string.Empty, _panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, GUIManager.Instance.AveriaSerifBold, 16, GUIManager.Instance.ValheimOrange, true, Color.black, 340f, 500f, false).GetComponent(); _text.alignment = (TextAnchor)0; _text.supportRichText = true; } private static int Stepped() { if (SelectorKeys.Pressed("stave_next")) { return 1; } if (!SelectorKeys.Pressed("stave_previous")) { return 0; } return -1; } private static bool Confirmed() { return SelectorKeys.Pressed("stave_confirm"); } private static bool Cancelled() { return SelectorKeys.Pressed("stave_cancel"); } } } namespace Stavebound.Travel { internal static class ApproachWarning { private static ZDOID _warnedAbout = ZDOID.None; internal static void Consider(ZDO portal, ZDO destination, Player nearby, bool allowed) { //IL_0037: 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_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) if (portal == null) { return; } if ((Object)(object)nearby == (Object)null || (Object)(object)nearby != (Object)(object)Player.m_localPlayer) { Forget(portal); } else if (allowed || !StaveboundConfig.WarnOnApproach.Value) { Forget(portal); } else if (!(_warnedAbout == portal.m_uid)) { ClearanceGate.Refusal refusal = ClearanceGate.FirstRefusal(((Humanoid)nearby).GetInventory(), ClearanceGate.EffectiveMask(portal, destination)); if (refusal != null) { _warnedAbout = portal.m_uid; ((Character)nearby).Message((MessageType)2, ClearanceGate.Explain(refusal, destination.GetString(ZDOVars.s_tag, string.Empty)), 0, (Sprite)null); } } } private static void Forget(ZDO portal) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (_warnedAbout == portal.m_uid) { _warnedAbout = ZDOID.None; } } internal static void Reset() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) _warnedAbout = ZDOID.None; } } internal static class CargoPreview { internal static bool TryGetNearbyDestination(out Clearance mask, out bool allowsEverything) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) mask = Clearance.None; allowsEverything = false; if (!StaveboundConfig.ShowBlockedCargoOverlay.Value || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } TeleportWorld val = PortalTarget.FindNearest(((Component)Player.m_localPlayer).transform.position, StaveboundConfig.CargoPreviewRange.Value); if ((Object)(object)val == (Object)null) { return false; } if (val.m_allowAllItems) { allowsEverything = true; return true; } ZDO val2 = PortalTarget.ZdoOf(val); if (!ClearanceGate.TryResolveDestination(val2, out var destination, out var _) || destination == null) { return false; } mask = ClearanceGate.EffectiveMask(val2, destination); return true; } internal static bool WouldBeRefused(ItemData item, Clearance destinationMask, bool allowsEverything) { if (item?.m_shared == null || item.m_shared.m_teleportable) { return false; } if (allowsEverything) { return false; } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); return !destinationMask.Permits(TierMap.RequiredFor(prefabName)); } } internal static class ClearanceGate { internal sealed class Refusal { internal string Item; internal Clearance Missing; internal int OtherStacks; } internal static bool TryResolveDestination(ZDO portal, out ZDO destination, out long pendingPid) { //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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) destination = null; pendingPid = 0L; if (portal == null || ZDOMan.instance == null) { return false; } long destination2 = PortalTarget.GetDestination(portal); if (destination2 != 0L) { if (!PortalRegistry.TryGet(destination2, out var record)) { return false; } pendingPid = destination2; destination = ZDOMan.instance.GetZDO(record.Id); return true; } ZDOID connectionZDOID = portal.GetConnectionZDOID((ConnectionType)1); if (((ZDOID)(ref connectionZDOID)).IsNone()) { return false; } destination = ZDOMan.instance.GetZDO(connectionZDOID); return true; } internal static bool Allows(Player player, ZDO source, ZDO destination, bool allowAllItems) { if (allowAllItems) { return true; } if ((Object)(object)player != (Object)null) { return FirstRefusal(((Humanoid)player).GetInventory(), EffectiveMask(source, destination)) == null; } return false; } internal static Refusal FirstRefusal(Inventory inventory, Clearance permitted) { if (inventory == null) { return null; } Refusal refusal = null; int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared == null || allItem.m_shared.m_teleportable) { continue; } string text = (((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : null); Clearance clearance = TierMap.RequiredFor(text); if (!permitted.Permits(clearance)) { if (refusal == null) { refusal = new Refusal { Item = ((Localization.instance != null) ? Localization.instance.Localize(allItem.m_shared.m_name) : text), Missing = clearance }; } else { num++; } } } if (refusal != null) { refusal.OtherStacks = num; } return refusal; } internal static string Explain(Refusal refusal, string destinationName) { string text = (string.IsNullOrEmpty(destinationName) ? Translations.Get("stave_that_portal") : ("\"" + destinationName + "\"")); string text2 = Translations.Format("stave_refusal", refusal.Item, text, refusal.Missing.StaveName()); if (refusal.OtherStacks > 0) { text2 += Translations.Format("stave_refusal_more", refusal.OtherStacks); } return text2; } internal static Clearance MaskOf(ZDO portal) { if (portal != null) { return (Clearance)portal.GetInt(ZdoKeys.ClearanceMask, 0); } return Clearance.None; } internal static Clearance EffectiveMask(ZDO source, ZDO destination) { return EffectiveMask(MaskOf(source), MaskOf(destination)); } internal static Clearance EffectiveMask(Clearance source, Clearance destination) { MaterialFlow num = StaveboundConfig.Flow?.Value ?? MaterialFlow.Both; Clearance clearance = Clearance.None; if (num != MaterialFlow.Deliver) { clearance |= destination; } if (num != MaterialFlow.Receive) { clearance |= source; } return clearance; } } internal static class SeamlessTransit { private const float VanillaPause = 2f; private static readonly FieldRef TeleportTimer = AccessTools.FieldRefAccess("m_teleportTimer"); internal static bool Enabled => StaveboundConfig.SeamlessTransit.Value; internal static bool DestinationIsLoaded(Vector3 destination) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNetScene.instance != (Object)null) { return ZNetScene.instance.IsAreaReady(destination); } return false; } internal static bool NeedsLoadingScreen(Vector3 destination) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (Enabled) { return !DestinationIsLoaded(destination); } return true; } internal static void ShortenPause(Player player, Vector3 destination) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !NeedsLoadingScreen(destination)) { float num = Mathf.Clamp(StaveboundConfig.TransitPause.Value, 0f, 2f); TeleportTimer.Invoke(player) = 2f - num; } } internal static bool ArrivalIsReady(Vector3 destination) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (Enabled) { return DestinationIsLoaded(destination); } return false; } } } namespace Stavebound.Tiers { internal sealed class BlockedItemsCommand : StaveboundCommand { public override string Name => "stave_items"; public override string Help => "List every item the game refuses to teleport, grouped by the stave that will permit it. Anything unrecognised is listed separately and defaults to the highest tier."; protected override void Execute(string[] args, Terminal context) { if (!TierMap.Built) { StaveboundCommand.Echo(context, "Stavebound: the tier map has not been built yet. Load a world first — the main menu's item database is only a partial one."); return; } StaveboundCommand.Echo(context, $"Stavebound tiers - {TierMap.BlockedCount} blocked item(s):"); Clearance[] array = new Clearance[5] { Clearance.Elder, Clearance.Bonemass, Clearance.Moder, Clearance.Yagluth, Clearance.Ashen }; foreach (Clearance tier in array) { List list = TierMap.AtTier(tier).Select(TierMap.Describe).ToList(); StaveboundCommand.Echo(context, $" {tier.StaveName()} ({list.Count}): " + ((list.Count == 0) ? "nothing" : string.Join(", ", list))); } IReadOnlyList unclassifiedItems = TierMap.UnclassifiedItems; if (unclassifiedItems.Count == 0) { StaveboundCommand.Echo(context, " Every blocked item is classified."); return; } StaveboundCommand.Echo(context, $" UNCLASSIFIED ({unclassifiedItems.Count}), defaulting to " + Clearance.Ashen.StaveName() + ":"); foreach (string item in unclassifiedItems.OrderBy((string n) => n)) { StaveboundCommand.Echo(context, " " + TierMap.Describe(item)); } StaveboundCommand.Echo(context, " Add these to the 2 - Clearance config lists to place them properly."); } } [Flags] internal enum Clearance { None = 0, Elder = 1, Bonemass = 2, Moder = 4, Yagluth = 8, Queen = 0x10, Ashen = 0x20 } internal static class ClearanceExtensions { internal const Clearance Highest = Clearance.Ashen; internal const Clearance All = Clearance.Elder | Clearance.Bonemass | Clearance.Moder | Clearance.Yagluth | Clearance.Queen | Clearance.Ashen; internal static readonly Clearance[] Ladder = new Clearance[6] { Clearance.Elder, Clearance.Bonemass, Clearance.Moder, Clearance.Yagluth, Clearance.Queen, Clearance.Ashen }; internal static Clearance UpToFirstGap(Clearance mask) { Clearance clearance = Clearance.None; Clearance[] ladder = Ladder; foreach (Clearance clearance2 in ladder) { if ((mask & clearance2) != clearance2) { break; } clearance |= clearance2; } return clearance; } internal static bool Permits(this Clearance mask, Clearance required) { if (required != Clearance.None) { return (mask & required) == required; } return true; } internal static string Symbol(this Clearance tier) { return tier switch { Clearance.Elder => "Cu", Clearance.Bonemass => "Fe", Clearance.Moder => "Ag", Clearance.Yagluth => "Bm", Clearance.Queen => "Dv", Clearance.Ashen => "Fl", _ => "--", }; } internal static string StaveName(this Clearance tier) { return Translations.Get(tier.NameToken()); } internal static string NameToken(this Clearance tier) { return tier switch { Clearance.Elder => "stave_rune_elder", Clearance.Bonemass => "stave_rune_bonemass", Clearance.Moder => "stave_rune_moder", Clearance.Yagluth => "stave_rune_yagluth", Clearance.Queen => "stave_rune_queen", Clearance.Ashen => "stave_rune_ashen", _ => "stave_rune_none", }; } } internal sealed class PrefabInspectCommand : StaveboundCommand { private const int MaxDepth = 4; public override string Name => "stave_inspect"; public override string Help => "stave_inspect - show a prefab's child objects, renderers, materials and shader colour properties. Use it to find out what can be lit or tinted separately."; protected override void Execute(string[] args, Terminal context) { string text = string.Join(" ", args).Trim(); if (text.Length == 0) { StaveboundCommand.Echo(context, "Stavebound: name a prefab, e.g. stave_inspect portal_wood"); return; } if ((Object)(object)ZNetScene.instance == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no world loaded."); return; } GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no prefab called \"" + text + "\". Try stave_prefabs " + text); return; } StaveboundCommand.Echo(context, "Stavebound inspect - " + ((Object)prefab).name); Describe(context, prefab.transform, 0); } private static void Describe(Terminal context, Transform node, int depth) { string text = new string(' ', (depth + 1) * 2); List list = (from c in ((Component)node).GetComponents() where (Object)(object)c != (Object)null && !(c is Transform) select ((object)c).GetType().Name).ToList(); StringBuilder stringBuilder = new StringBuilder(text + ((Object)node).name); if (list.Count > 0) { stringBuilder.Append(" [" + string.Join(", ", list) + "]"); } if (!((Component)node).gameObject.activeSelf) { stringBuilder.Append(" (inactive)"); } StaveboundCommand.Echo(context, stringBuilder.ToString()); Renderer component = ((Component)node).GetComponent(); if ((Object)(object)component != (Object)null) { foreach (Material item in component.sharedMaterials.Where((Material m) => (Object)(object)m != (Object)null)) { string[] obj = new string[7] { text, " material '", ((Object)item).name, "' shader '", null, null, null }; Shader shader = item.shader; obj[4] = ((shader != null) ? ((Object)shader).name : null); obj[5] = "'"; obj[6] = DescribeColours(item); StaveboundCommand.Echo(context, string.Concat(obj)); } } if (depth >= 4) { if (node.childCount > 0) { StaveboundCommand.Echo(context, $"{text} ... {node.childCount} more child object(s), deeper than this dump goes"); } } else { for (int num = 0; num < node.childCount; num++) { Describe(context, node.GetChild(num), depth + 1); } } } private static string DescribeColours(Material material) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) List list = new List(); string[] array = new string[4] { "_Color", "_EmissionColor", "_EmissiveColor", "_TintColor" }; foreach (string text in array) { try { if (material.HasProperty(text)) { list.Add($"{text}={material.GetColor(text)}"); } } catch (Exception) { } } if (list.Count != 0) { return " " + string.Join(" ", list); } return string.Empty; } } internal sealed class PrefabPreviewCommand : StaveboundCommand { private static readonly List Previews = new List(); public override string Name => "stave_preview"; public override string Help => "stave_preview - place a look-at-only copy in front of you. It is not saved, not networked and vanishes on reload. 'stave_preview clear' removes them. For anything spawnable, use vanilla spawn instead."; protected override void Execute(string[] args, Terminal context) { //IL_00e3: 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: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) string text = string.Join(" ", args).Trim(); if (string.Equals(text, "clear", StringComparison.OrdinalIgnoreCase)) { Clear(context); return; } if (text.Length == 0) { StaveboundCommand.Echo(context, "Stavebound: name a prefab, or 'clear' to remove the previews."); return; } if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no world loaded."); return; } GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no prefab called \"" + text + "\". Try stave_prefabs " + text); } else if ((Object)(object)prefab.GetComponent() != (Object)null) { StaveboundCommand.Echo(context, "Stavebound: " + ((Object)prefab).name + " is a networked object - use 'spawn " + ((Object)prefab).name + "' instead, which places it properly and can be removed with the hammer."); } else { Transform transform = ((Component)Player.m_localPlayer).transform; Vector3 val = transform.position + transform.forward * 3f; GameObject val2 = Object.Instantiate(prefab, val, transform.rotation); ((Object)val2).name = "stave_preview_" + ((Object)prefab).name; Previews.Add(val2); StaveboundCommand.Echo(context, "Stavebound: previewing " + ((Object)prefab).name + " in front of you " + $"({Previews.Count} up). This is scenery only - nothing was added to the world."); } } private static void Clear(Terminal context) { int num = Previews.Count((GameObject p) => (Object)(object)p != (Object)null); foreach (GameObject item in Previews.Where((GameObject p) => (Object)(object)p != (Object)null)) { Object.Destroy((Object)(object)item); } Previews.Clear(); StaveboundCommand.Echo(context, (num == 0) ? "Stavebound: nothing was being previewed." : $"Stavebound: removed {num} preview(s)."); } } internal sealed class PrefabSearchCommand : StaveboundCommand { private const int Limit = 60; public override string Name => "stave_prefabs"; public override string Help => "stave_prefabs [,...] - list loaded prefabs whose name contains each term, marking which are buildable pieces and which are items. Comma-separate to search for several things at once."; protected override void Execute(string[] args, Terminal context) { if ((Object)(object)ZNetScene.instance == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no world loaded - prefabs only exist once you are in a game."); return; } string[] array = (from t in string.Join(" ", args).Split(new char[1] { ',' }) select t.Trim() into t where t.Length > 0 select t).ToArray(); if (array.Length == 0) { StaveboundCommand.Echo(context, "Stavebound: give me something to search for, e.g. stave_prefabs trophy,sconce,brazier"); return; } string[] array2 = array; foreach (string term in array2) { Search(context, term); } } private static void Search(Terminal context, string term) { List list = (from p in ZNetScene.instance.m_prefabs where (Object)(object)p != (Object)null && ((Object)p).name.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0 orderby ((Object)p).name select p).ToList(); if (list.Count == 0) { StaveboundCommand.Echo(context, "\"" + term + "\": nothing."); return; } StaveboundCommand.Echo(context, $"\"{term}\": {list.Count}" + ((list.Count > 60) ? $" (showing {60})" : string.Empty)); foreach (GameObject item in list.Take(60)) { List list2 = new List(); if ((Object)(object)item.GetComponent() != (Object)null) { list2.Add("piece"); } ItemDrop component = item.GetComponent(); if (component?.m_itemData?.m_shared != null) { list2.Add("item \"" + Localization.instance.Localize(component.m_itemData.m_shared.m_name) + "\""); } StaveboundCommand.Echo(context, " " + ((Object)item).name + ((list2.Count > 0) ? (" [" + string.Join(", ", list2) + "]") : string.Empty)); } } } internal static class TierMap { private static readonly Dictionary Required = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly List Unclassified = new List(); private static readonly Dictionary DisplayTokens = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static bool Built { get; private set; } internal static int BlockedCount => Required.Count; internal static IReadOnlyList UnclassifiedItems => Unclassified; internal static Clearance RequiredFor(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return Clearance.None; } if (!Required.TryGetValue(prefabName, out var value)) { return Clearance.None; } return value; } internal static IEnumerable AtTier(Clearance tier) { return from pair in Required where pair.Value == tier select pair.Key into name orderby name select name; } internal static string Describe(string prefabName) { if (!DisplayTokens.TryGetValue(prefabName, out var value) || string.IsNullOrEmpty(value)) { return prefabName; } string text = ((Localization.instance != null) ? Localization.instance.Localize(value) : value); if (!string.Equals(text, prefabName, StringComparison.OrdinalIgnoreCase)) { return prefabName + " (" + text + ")"; } return prefabName; } internal static void Build() { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0) { return; } Required.Clear(); Unclassified.Clear(); DisplayTokens.Clear(); Dictionary dictionary = ReadConfiguredTiers(); foreach (GameObject item in instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent(); if (!((Object)(object)component == (Object)null) && component.m_itemData?.m_shared != null && !component.m_itemData.m_shared.m_teleportable) { string name = ((Object)item).name; DisplayTokens[name] = component.m_itemData.m_shared.m_name; if (dictionary.TryGetValue(name, out var value)) { Required[name] = value; continue; } Required[name] = Clearance.Ashen; Unclassified.Add(name); } } Built = true; Logger.LogInfo((object)$"Tier map built: {Required.Count} blocked item(s) across {instance.m_items.Count} in ObjectDB."); if (Unclassified.Count > 0) { Logger.LogWarning((object)($"{Unclassified.Count} blocked item(s) are not in the tier config and default to " + $"{Clearance.Ashen} ({Clearance.Ashen.StaveName()}): " + string.Join(", ", Unclassified.OrderBy((string n) => n)) + ". Add them to the 2 - Clearance section to place them properly.")); } } private static Dictionary ReadConfiguredTiers() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair item in StaveboundConfig.TierPrefabs()) { string[] array = item.Value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { if (dictionary.TryGetValue(text, out var value) && value != item.Key) { Logger.LogWarning((object)($"'{text}' is listed under both {value} and {item.Key}. Using {value}; " + "remove one of them.")); } else { dictionary[text] = item.Key; } } } } return dictionary; } } } namespace Stavebound.Staves { internal static class CoreTint { private static readonly string[] ColourProperties = new string[3] { "_EmissionColor", "_Color", "_TintColor" }; internal static void Apply(GameObject prefab, Color tint, string describe) { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)prefab == (Object)null) { return; } List list = new List(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { Material[] sharedMaterials = val.sharedMaterials; bool flag = false; for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; if ((Object)(object)val2 == (Object)null || !IsEmissive(val2)) { continue; } Material val3 = new Material(val2) { name = ((Object)val2).name + "_" + describe }; string[] colourProperties = ColourProperties; foreach (string text in colourProperties) { if (val3.HasProperty(text)) { val3.SetColor(text, (text == "_EmissionColor") ? AtIntensityOf(tint, val2.GetColor(text)) : tint); } } sharedMaterials[j] = val3; flag = true; } if (flag) { val.sharedMaterials = sharedMaterials; list.Add("material on " + ((Object)val).name); } } Light[] componentsInChildren2 = prefab.GetComponentsInChildren(true); foreach (Light val4 in componentsInChildren2) { val4.color = tint; list.Add("light on " + ((Object)val4).name); } ParticleSystem[] componentsInChildren3 = prefab.GetComponentsInChildren(true); foreach (ParticleSystem val5 in componentsInChildren3) { MainModule main = val5.main; ((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(tint); list.Add("particles on " + ((Object)val5).name); } Logger.LogInfo((object)((list.Count == 0) ? ("Tint " + describe + ": found nothing emissive on " + ((Object)prefab).name + " - the glow is not where this expects it.") : string.Format("Tint {0} on {1}: {2} target(s) - {3}", describe, ((Object)prefab).name, list.Count, string.Join(", ", list.Distinct())))); } private static Color AtIntensityOf(Color tint, Color original) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) float maxColorComponent = ((Color)(ref original)).maxColorComponent; float maxColorComponent2 = ((Color)(ref tint)).maxColorComponent; if (maxColorComponent <= 0f || maxColorComponent2 <= 0f) { return tint; } float num = maxColorComponent / maxColorComponent2; return new Color(tint.r * num, tint.g * num, tint.b * num, tint.a); } private static bool IsEmissive(Material material) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (material.IsKeywordEnabled("_EMISSION") || material.IsKeywordEnabled("_EMISSIVE")) { return true; } if (material.HasProperty("_EmissionColor")) { Color color = material.GetColor("_EmissionColor"); return ((Color)(ref color)).maxColorComponent > 0.01f; } return false; } } internal static class PlacementFeedback { private const float SearchInterval = 0.2f; private static readonly Vector3 BeamLift = Vector3.up * 1.2f; private static GameObject _circle; private static CircleProjector _projector; private static GameObject _beam; private static bool _beamUnavailable; private static float _nextSearch; private static readonly List InRange = new List(); private static string _lastAnswer; internal static void Update(GameObject ghost) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0096: 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) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ghost == (Object)null || !IsOurs(ghost)) { Hide(); return; } Vector3 position = ghost.transform.position; float value = StaveboundConfig.StaveRadius.Value; Search(position, value); bool num = StaveboundConfig.Binding.Value == PortalBinding.AllInRadius; TeleportWorld val = ((InRange.Count > 0) ? InRange[0] : null); if (!num && (Object)(object)val != (Object)null) { ShowBeam(position + BeamLift, ((Component)val).transform.position + BeamLift); HideCircle(); } else { ShowCircle(position, value); HideBeam(); } Announce(num); } internal static void Reset() { if ((Object)(object)_circle != (Object)null) { Object.Destroy((Object)(object)_circle); } if ((Object)(object)_beam != (Object)null) { Object.Destroy((Object)(object)_beam); } _circle = null; _projector = null; _beam = null; _beamUnavailable = false; _lastAnswer = null; _nextSearch = 0f; InRange.Clear(); } private static bool IsOurs(GameObject ghost) { return StavePieces.ClearanceOf(((Object)ghost).name.Replace("(Clone)", string.Empty).Trim()) != Clearance.None; } private static void Search(Vector3 at, float radius) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (Time.time < _nextSearch) { return; } _nextSearch = Time.time + 0.2f; float limit = radius * radius; InRange.Clear(); InRange.AddRange(Object.FindObjectsByType((FindObjectsSortMode)0).Where(delegate(TeleportWorld p) { //IL_000f: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)p != (Object)null) { Vector3 val = ((Component)p).transform.position - at; return ((Vector3)(ref val)).sqrMagnitude <= limit; } return false; }).OrderBy(delegate(TeleportWorld p) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)p).transform.position - at; return ((Vector3)(ref val)).sqrMagnitude; })); } private static void ShowBeam(Vector3 from, Vector3 to) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_beam == (Object)null && !BuildBeam(from)) { return; } Vector3 val = to - from; if (!(((Vector3)(ref val)).sqrMagnitude < 0.001f)) { _beam.transform.position = from; _beam.transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized); _beam.transform.localScale = new Vector3(1f, 1f, ((Vector3)(ref val)).magnitude); if (!_beam.activeSelf) { _beam.SetActive(true); } } } private static bool BuildBeam(Vector3 at) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (_beamUnavailable) { return false; } StationExtension val = ((IEnumerable)Resources.FindObjectsOfTypeAll()).FirstOrDefault((Func)((StationExtension e) => (Object)(object)e != (Object)null && (Object)(object)e.m_connectionPrefab != (Object)null)); if ((Object)(object)val == (Object)null) { _beamUnavailable = true; Logger.LogWarning((object)"No station-connection effect to borrow, so stave binding will be shown as a circle instead of a beam."); return false; } _beam = Object.Instantiate(val.m_connectionPrefab, at, Quaternion.identity); ((Object)_beam).name = "stave_binding_beam"; return true; } private static void HideBeam() { if ((Object)(object)_beam != (Object)null && _beam.activeSelf) { _beam.SetActive(false); } } private static void ShowCircle(Vector3 at, float radius) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_projector == (Object)null) || BuildCircle()) { _circle.transform.position = at; _projector.m_radius = radius; if (!_circle.activeSelf) { _circle.SetActive(true); } } } private static bool BuildCircle() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) CircleProjector val = ((IEnumerable)Resources.FindObjectsOfTypeAll()).FirstOrDefault((Func)((CircleProjector c) => (Object)(object)c != (Object)null && (Object)(object)c.m_prefab != (Object)null)); if ((Object)(object)val == (Object)null) { Logger.LogWarning((object)"No CircleProjector to borrow, so stave range will not be drawn. The portal it binds to is still named on placement."); return false; } _circle = new GameObject("stave_range"); _projector = _circle.AddComponent(); _projector.m_prefab = val.m_prefab; _projector.m_mask = val.m_mask; _projector.m_nrOfSegments = val.m_nrOfSegments; return true; } private static void HideCircle() { if ((Object)(object)_circle != (Object)null && _circle.activeSelf) { _circle.SetActive(false); } } private static void Hide() { HideCircle(); HideBeam(); _lastAnswer = null; } private static void Announce(bool all) { string text = Describe(all); if (!(text == _lastAnswer)) { _lastAnswer = text; Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null); } } } private static string Describe(bool all) { if (InRange.Count == 0) { return Translations.Get("stave_place_no_portal"); } if (all) { if (InRange.Count != 1) { return Translations.Format("stave_place_binds_all", InRange.Count); } return Translations.Format("stave_place_binds", Name(InRange[0])); } if (InRange.Count != 1) { return Translations.Format("stave_place_binds_nearest", Name(InRange[0]), InRange.Count); } return Translations.Format("stave_place_binds", Name(InRange[0])); } private static string Name(TeleportWorld portal) { ZDO val = PortalTarget.ZdoOf(portal); string text = ((val != null) ? val.GetString(ZDOVars.s_tag, string.Empty) : string.Empty); if (!string.IsNullOrEmpty(text)) { return "\"" + text + "\""; } return Translations.Get("stave_an_unnamed_portal"); } } internal static class SiteSweep { private const float SweepSeconds = 10f; private static Coroutine _sweep; private static readonly List Found = new List(); private static readonly Dictionary PortalMasks = new Dictionary(); internal static void Start() { Stop(); if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { _sweep = ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run()); } } internal static void Stop() { if (_sweep != null && (Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(_sweep); } _sweep = null; Found.Clear(); PortalMasks.Clear(); } private static IEnumerator Run() { WaitForSeconds wait = new WaitForSeconds(10f); while (true) { yield return Recompute(); yield return wait; } } private static IEnumerator Recompute() { if (ZDOMan.instance == null) { yield break; } PortalMasks.Clear(); float radius = StaveboundConfig.StaveRadius.Value; bool all = StaveboundConfig.Binding.Value == PortalBinding.AllInRadius; foreach (KeyValuePair stave in StavePieces.Staves) { yield return Collect(stave.Key, Found); foreach (ZDO item in Found) { Bind(item.GetPosition(), stave.Value, radius, all); } } if (StaveboundConfig.StrictLadder.Value) { foreach (ZDO item2 in new List(PortalMasks.Keys)) { PortalMasks[item2] = ClearanceExtensions.UpToFirstGap(PortalMasks[item2]); } } WriteChangedMasks(); } private static void Bind(Vector3 runePosition, Clearance tier, float radius, bool all) { //IL_0040: 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_0046: 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) ZDOMan instance = ZDOMan.instance; List list = ((instance != null) ? instance.GetPortals() : null); if (list == null) { return; } float num = radius * radius; ZDO val = null; float num2 = num; foreach (ZDO item in list) { if (item == null || !item.IsValid()) { continue; } Vector3 val2 = item.GetPosition() - runePosition; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (!(sqrMagnitude > num)) { if (all) { Grant(item, tier); } else if (sqrMagnitude <= num2) { num2 = sqrMagnitude; val = item; } } } if (!all && val != null) { Grant(val, tier); } } private static IEnumerator Collect(string prefab, List into) { into.Clear(); int index = 0; bool complete = false; while (!complete) { if (ZDOMan.instance == null) { into.Clear(); break; } complete = ZDOMan.instance.GetAllZDOsWithPrefabIterative(prefab, into, ref index); yield return null; } } private static void Grant(ZDO portal, Clearance tier) { PortalMasks[portal] = (PortalMasks.TryGetValue(portal, out var value) ? (value | tier) : tier); } private static void WriteChangedMasks() { ZDOMan instance = ZDOMan.instance; List list = ((instance != null) ? instance.GetPortals() : null); if (list == null) { return; } foreach (ZDO item in list) { if (item != null && item.IsValid()) { PortalMasks.TryGetValue(item, out var value); Write(item, value); } } } private static void Write(ZDO portal, Clearance mask) { if (portal.GetInt(ZdoKeys.ClearanceMask, 0) != (int)mask) { PortalTarget.Claim(portal); portal.Set(ZdoKeys.ClearanceMask, (int)mask, false); PortalTarget.Publish(portal); string text = portal.GetString(ZDOVars.s_tag, string.Empty); Logger.LogInfo((object)("[site] Clearance of " + (string.IsNullOrEmpty(text) ? "an unnamed portal" : ("\"" + text + "\"")) + " is now " + Describe(mask) + ".")); } } private static string Describe(Clearance mask) { if (mask == Clearance.None) { return "nothing"; } List list = new List(); Clearance[] ladder = ClearanceExtensions.Ladder; for (int i = 0; i < ladder.Length; i++) { Clearance clearance = ladder[i]; if ((mask & clearance) == clearance) { list.Add(clearance.ToString()); } } return string.Join(" + ", list); } } internal static class StavePieces { private sealed class PieceSpec { internal string Name; internal string Display; internal string Description; internal Color Tint; internal RequirementConfig[] Requirements; } private const string CloneSource = "Pickable_BlackCoreStand"; private const string TrophyElder = "TrophyTheElder"; private const string TrophyBonemass = "TrophyBonemass"; private const string TrophyModer = "TrophyDragonQueen"; private const string TrophyYagluth = "TrophyGoblinKing"; private const string TrophyQueen = "TrophySeekerQueen"; private const string TrophyFader = "TrophyFader"; private static readonly List Specs = new List { new PieceSpec { Name = "stave_elder", Display = "stave_rune_elder", Description = "stave_desc_elder", Tint = new Color(0.95f, 0.8f, 0.15f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophyTheElder", Amount = 1, Recover = true }, new RequirementConfig { Item = "Copper", Amount = 10, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } }, new PieceSpec { Name = "stave_bonemass", Display = "stave_rune_bonemass", Description = "stave_desc_bonemass", Tint = new Color(0.25f, 0.85f, 0.3f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophyBonemass", Amount = 1, Recover = true }, new RequirementConfig { Item = "Iron", Amount = 10, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } }, new PieceSpec { Name = "stave_moder", Display = "stave_rune_moder", Description = "stave_desc_moder", Tint = new Color(0.75f, 0.85f, 1f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophyDragonQueen", Amount = 1, Recover = true }, new RequirementConfig { Item = "Silver", Amount = 10, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } }, new PieceSpec { Name = "stave_yagluth", Display = "stave_rune_yagluth", Description = "stave_desc_yagluth", Tint = new Color(0.45f, 0.15f, 0.65f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophyGoblinKing", Amount = 1, Recover = true }, new RequirementConfig { Item = "BlackMetal", Amount = 10, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } }, new PieceSpec { Name = "stave_queen", Display = "stave_rune_queen", Description = "stave_desc_queen", Tint = new Color(0.4f, 0.95f, 0.95f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophySeekerQueen", Amount = 1, Recover = true }, new RequirementConfig { Item = "DvergrNeedle", Amount = 3, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } }, new PieceSpec { Name = "stave_ashen", Display = "stave_rune_ashen", Description = "stave_desc_ashen", Tint = new Color(1f, 0.35f, 0.1f), Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "TrophyFader", Amount = 1, Recover = true }, new RequirementConfig { Item = "FlametalNew", Amount = 10, Recover = true }, new RequirementConfig { Item = "Stone", Amount = 20, Recover = true } } } }; private static readonly Dictionary Granted = new Dictionary { { "stave_elder", Clearance.Elder }, { "stave_bonemass", Clearance.Bonemass }, { "stave_moder", Clearance.Moder }, { "stave_yagluth", Clearance.Yagluth }, { "stave_queen", Clearance.Queen }, { "stave_ashen", Clearance.Ashen } }; internal static IEnumerable> Staves => Granted; internal static Clearance ClearanceOf(string prefabName) { if (!Granted.TryGetValue(prefabName, out var value)) { return Clearance.None; } return value; } internal static void Register() { PrefabManager.OnVanillaPrefabsAvailable += Create; } private static void Create() { PrefabManager.OnVanillaPrefabsAvailable -= Create; if ((Object)(object)PrefabManager.Instance.GetPrefab("Pickable_BlackCoreStand") == (Object)null) { Logger.LogError((object)"'Pickable_BlackCoreStand' is missing, so no staves can be built. A game update may have renamed it — run stave_prefabs corestand to find its new name."); return; } foreach (PieceSpec spec in Specs) { try { CreateOne(spec); } catch (Exception arg) { Logger.LogError((object)$"Could not create {spec.Name}: {arg}"); } } } private static void CheckRequirements(PieceSpec spec) { RequirementConfig[] requirements = spec.Requirements; foreach (RequirementConfig val in requirements) { if ((Object)(object)PrefabManager.Instance.GetPrefab(val.Item) == (Object)null) { Logger.LogError((object)(spec.Name + " needs '" + val.Item + "', which does not exist. That piece will be unbuildable until the name is corrected - try stave_prefabs to find it.")); } } } private static void CreateOne(PieceSpec spec) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown CheckRequirements(spec); GameObject val = PrefabManager.Instance.CreateClonedPrefab(spec.Name, "Pickable_BlackCoreStand"); if ((Object)(object)val == (Object)null) { Logger.LogError((object)("Could not clone Pickable_BlackCoreStand for " + spec.Name + ".")); return; } Pickable component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } MakeBuildable(val); CoreTint.Apply(val, spec.Tint, spec.Name); CustomPiece val2 = new CustomPiece(val, false, new PieceConfig { Name = "$" + spec.Display, Description = "$" + spec.Description, PieceTable = PieceTables.Hammer, Category = "Misc", Icon = RenderIcon(val, spec.Name), Requirements = spec.Requirements }); if (!val2.IsValid()) { Logger.LogError((object)(spec.Name + " did not come out valid and will not be registered.")); return; } PieceManager.Instance.AddPiece(val2); Logger.LogInfo((object)("Registered piece " + spec.Name + ".")); } private static Sprite RenderIcon(GameObject prefab, string describe) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown try { Sprite obj = RenderManager.Instance.Render(new RenderRequest(prefab) { Rotation = RenderManager.IsometricRotation, Width = 128, Height = 128, UseCache = false }); if ((Object)(object)obj == (Object)null) { Logger.LogWarning((object)("No icon could be rendered for " + describe + ".")); } return obj; } catch (Exception ex) { Logger.LogWarning((object)("Icon render failed for " + describe + ": " + ex.Message)); return null; } } private static void MakeBuildable(GameObject prefab) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) Piece obj = prefab.GetComponent() ?? prefab.AddComponent(); obj.m_canBeRemoved = true; obj.m_canRotate = true; obj.m_allowedInDungeons = false; obj.m_groundPiece = false; obj.m_groundOnly = false; obj.m_noInWater = true; if (!((Object)(object)prefab.GetComponent() != (Object)null)) { WearNTear obj2 = prefab.AddComponent(); obj2.m_health = 500f; obj2.m_burnable = false; obj2.m_noRoofWear = true; obj2.m_noSupportWear = true; obj2.m_materialType = (MaterialType)1; } } } } namespace Stavebound.Portals { internal sealed class PortalAimCommand : StaveboundCommand { private const float Range = 10f; public override string Name => "stave_aim"; public override string Help => "stave_aim - point the nearest portal at the portal with that name. With no arguments, clears the target and hands the portal back to vanilla tag pairing."; protected override void Execute(string[] args, Terminal context) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no player."); return; } TeleportWorld val = PortalTarget.FindNearest(((Component)Player.m_localPlayer).transform.position, 10f); if ((Object)(object)val == (Object)null) { StaveboundCommand.Echo(context, $"Stavebound: no portal within {10f:F0}m. Stand at the one you want to re-aim."); return; } ZDO val2 = PortalTarget.ZdoOf(val); if (val2 == null) { StaveboundCommand.Echo(context, "Stavebound: that portal is not ready yet."); return; } string text = val2.GetString(ZDOVars.s_tag, string.Empty); text = (string.IsNullOrEmpty(text) ? "the unnamed portal" : ("\"" + text + "\"")); string wanted = string.Join(" ", args).Trim(); if (wanted.Length == 0) { PortalTarget.Clear(val2); StaveboundCommand.Echo(context, "Stavebound: " + text + " now follows vanilla tag pairing again."); return; } List list = PortalRegistry.All.Where((PortalRecord p) => string.Equals(p.Name, wanted, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count == 0) { StaveboundCommand.Echo(context, "Stavebound: no portal named \"" + wanted + "\". Try stave_portals for the list."); return; } if (list.Count > 1) { StaveboundCommand.Echo(context, $"Stavebound: {list.Count} portals are named \"{wanted}\". Rename one, or wait for the map selector."); return; } PortalRecord portalRecord = list[0]; if (portalRecord.Pid == PortalTarget.GetPid(val2)) { StaveboundCommand.Echo(context, "Stavebound: a portal cannot point at itself."); return; } PortalTarget.Set(val2, portalRecord.Pid); StaveboundCommand.Echo(context, $"Stavebound: {text} now points at {portalRecord}. Nothing was written to the far side - walk back and you will not return here unless it points at you."); } public override List CommandOptionList() { return (from n in (from p in PortalRegistry.All select p.Name into n where !string.IsNullOrEmpty(n) select n).Distinct() orderby n select n).ToList(); } } internal sealed class PortalNetCommand : StaveboundCommand { public override string Name => "stave_net"; public override string Help => "Report the portal registry's network state: role, peers, traffic, and whether every destination resolves. Run on the server and the client and compare."; protected override void Execute(string[] args, Terminal context) { if ((Object)(object)ZNet.instance == (Object)null) { StaveboundCommand.Echo(context, "Stavebound: no world loaded, so there is no sync to report on."); return; } bool num = ZNet.instance.IsServer(); bool flag = ZNet.instance.IsDedicated(); List list = ZNet.instance.GetConnectedPeers() ?? new List(); string text = ((!num) ? "CLIENT" : (flag ? "DEDICATED SERVER" : ((list.Count > 0) ? "HOST (server + client)" : "SINGLE PLAYER (own server)"))); StaveboundCommand.Echo(context, "Stavebound sync - " + text); StaveboundCommand.Echo(context, $" connected peers: {list.Count}" + ((list.Count > 0) ? (" (" + string.Join(", ", list.Select((ZNetPeer p) => p.m_uid.ToString())) + ")") : string.Empty)); StaveboundCommand.Echo(context, " " + PortalRegistry.Traffic); IReadOnlyList all = PortalRegistry.All; StaveboundCommand.Echo(context, $" portals known: {all.Count}"); if (num && list.Count == 0) { StaveboundCommand.Echo(context, " Note: nothing is broadcast with no peers connected, so an absence of traffic here is expected rather than a fault."); } if (!num && all.Count == 0) { StaveboundCommand.Echo(context, " A client with no portals has not been told anything. Either the server is not running Stavebound, or the initial sync did not arrive."); return; } List list2 = all.Where((PortalRecord p) => p.TargetPid != 0).ToList(); PortalRecord record; List list3 = list2.Where((PortalRecord p) => !PortalRegistry.TryGet(p.TargetPid, out record)).ToList(); StaveboundCommand.Echo(context, $" re-aimed: {list2.Count}, of which {list3.Count} point at something " + "this instance does not know about"); foreach (PortalRecord item in list3) { StaveboundCommand.Echo(context, $" {item} -> pid {item.TargetPid}, unresolved"); } } } internal readonly struct PortalRecord : IEquatable { internal long Pid { get; } internal ZDOID Id { get; } internal string Name { get; } internal Vector3 Position { get; } internal long TargetPid { get; } internal int ClearanceMask { get; } internal PortalRecord(long pid, ZDOID id, string name, Vector3 position, long targetPid, int clearanceMask) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Pid = pid; Id = id; Name = name ?? string.Empty; Position = position; TargetPid = targetPid; ClearanceMask = clearanceMask; } internal void WriteTo(ZPackage package) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) package.Write(Pid); package.Write(Id); package.Write(Name); package.Write(Position); package.Write(TargetPid); package.Write(ClearanceMask); } internal static PortalRecord ReadFrom(ZPackage package) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) long pid = package.ReadLong(); ZDOID id = package.ReadZDOID(); string name = package.ReadString(); Vector3 position = package.ReadVector3(); long targetPid = package.ReadLong(); int clearanceMask = package.ReadInt(); return new PortalRecord(pid, id, name, position, targetPid, clearanceMask); } public bool Equals(PortalRecord other) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (Pid == other.Pid && Id == other.Id && TargetPid == other.TargetPid && ClearanceMask == other.ClearanceMask && string.Equals(Name, other.Name, StringComparison.Ordinal)) { return Position == other.Position; } return false; } public override bool Equals(object obj) { if (obj is PortalRecord other) { return Equals(other); } return false; } public override int GetHashCode() { return Pid.GetHashCode(); } public override string ToString() { if (!string.IsNullOrEmpty(Name)) { return "\"" + Name + "\""; } return "(unnamed portal)"; } } internal static class PortalRegistry { private const byte WireVersion = 2; private const float ServerSweepSeconds = 2f; private static readonly List Ordered = new List(); private static readonly Dictionary ByPid = new Dictionary(); private static readonly List SweepBuffer = new List(); private static readonly HashSet SweepPids = new HashSet(); private static CustomRPC _rpc; private static Coroutine _sweep; private static int _lastSentBytes; private static int _lastReceivedBytes; private static int _receiveCount; internal static string Traffic => $"packages received: {_receiveCount}" + ((_receiveCount > 0) ? $" (last {_lastReceivedBytes} bytes)" : string.Empty) + ((_lastSentBytes > 0) ? $"; last broadcast {_lastSentBytes} bytes" : "; nothing broadcast yet"); internal static IReadOnlyList All => Ordered; internal static bool TryGet(long pid, out PortalRecord record) { return ByPid.TryGetValue(pid, out record); } internal static void Register() { //IL_0011: 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_0027: Expected O, but got Unknown //IL_0027: Expected O, but got Unknown _rpc = NetworkManager.Instance.AddRPC("stave_portals", new CoroutineHandler(OnServerReceive), new CoroutineHandler(OnClientReceive)); SynchronizationManager.Instance.AddInitialSynchronization(_rpc, (Func)BuildSnapshotForJoiningClient); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PortalRegistryCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PortalAimCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PortalNetCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BlockedItemsCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PrefabSearchCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PrefabInspectCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PrefabPreviewCommand()); } internal static void OnWorldStart() { Clear(); if (!IsServer()) { SyncLog.Say("World started as a CLIENT. Waiting for the server's portal list; nothing will be known until it arrives."); return; } bool flag = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated(); SyncLog.Say("World started as " + (flag ? "a DEDICATED SERVER" : "the SERVER (host or single player)") + ". " + $"Sweeping every {2f}s; broadcasting only on change."); _sweep = ((MonoBehaviour)Plugin.Instance).StartCoroutine(SweepRoutine()); } internal static void OnWorldEnd() { if (_sweep != null) { if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(_sweep); } _sweep = null; } Clear(); } private static void Clear() { Ordered.Clear(); ByPid.Clear(); SweepBuffer.Clear(); SweepPids.Clear(); } private static bool IsServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } private static IEnumerator SweepRoutine() { WaitForSeconds wait = new WaitForSeconds(2f); while (true) { if (RebuildFromWorld()) { Broadcast(); } yield return wait; } } private static bool RebuildFromWorld() { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) SweepBuffer.Clear(); SweepPids.Clear(); ZDOMan instance = ZDOMan.instance; List list = ((instance != null) ? instance.GetPortals() : null); if (list == null) { return false; } foreach (ZDO item in list) { if (item == null || !item.IsValid()) { continue; } long pid = PortalTarget.GetPid(item); long num = PortalTarget.EnsurePid(item, SweepPids.Contains); if (num != 0L) { if (pid != num) { SyncLog.Say((pid == 0L) ? $"Minted pid {num} for a portal at {item.GetPosition().x:F0},{item.GetPosition().z:F0}." : $"Re-minted pid {pid} -> {num}: another portal already claimed it."); } SweepPids.Add(num); SweepBuffer.Add(new PortalRecord(num, item.m_uid, item.GetString(ZDOVars.s_tag, string.Empty), item.GetPosition(), PortalTarget.GetDestination(item), item.GetInt(ZdoKeys.ClearanceMask, 0))); } } if (Matches(SweepBuffer)) { return false; } Apply(SweepBuffer); return true; } private static bool Matches(List candidate) { if (candidate.Count != Ordered.Count) { return false; } for (int i = 0; i < candidate.Count; i++) { if (!candidate[i].Equals(Ordered[i])) { return false; } } return true; } private static void Broadcast() { ZNet instance = ZNet.instance; List list = ((instance != null) ? instance.GetConnectedPeers() : null); if (list == null || list.Count == 0) { SyncLog.Say($"Change not broadcast - no connected peers. {Ordered.Count} portal(s) held locally."); return; } if (_rpc == null) { SyncLog.Warn("No RPC registered; connected clients will never receive the portal list."); return; } ZPackage val = BuildSnapshot(); _lastSentBytes = val.Size(); SyncLog.Say($"Broadcasting {Ordered.Count} portal(s), {_lastSentBytes} bytes, to {list.Count} peer(s): " + string.Join(", ", list.Select((ZNetPeer p) => p.m_uid.ToString()))); _rpc.SendPackage(list, val); } private static ZPackage BuildSnapshotForJoiningClient() { RebuildFromWorld(); ZPackage val = BuildSnapshot(); SyncLog.Say($"A client is joining - sending the initial portal list: {Ordered.Count} portal(s), " + $"{val.Size()} bytes. This is guaranteed to arrive before they load in."); return val; } private static ZPackage BuildSnapshot() { //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((byte)2); val.Write(Ordered.Count); foreach (PortalRecord item in Ordered) { item.WriteTo(val); } return val; } private static IEnumerator OnClientReceive(long sender, ZPackage package) { int num = package.Size(); byte b = package.ReadByte(); if (b != 2) { Logger.LogError((object)($"Ignoring a portal list in wire format {b}; this build speaks {(byte)2}. " + "The server and this client are running different Stavebound builds - portal destinations will not work until they match.")); yield break; } int num2 = package.ReadInt(); List list = new List(num2); for (int i = 0; i < num2; i++) { list.Add(PortalRecord.ReadFrom(package)); } SyncLog.Say($"Received {num2} portal(s) from the server (peer {sender}), {num} bytes, wire v{b}."); _lastReceivedBytes = num; _receiveCount++; Apply(list); if ((Object)(object)Player.m_localPlayer == (Object)null) { SyncLog.Say("Player has not spawned yet, so this is the join-time sync."); yield break; } Vector3 here = ((Component)Player.m_localPlayer).transform.position; int num3 = list.Count((PortalRecord p) => Vector3.Distance(here, p.Position) > 200f); SyncLog.Say($"Of those, {num3} are more than 200m away - portals this client could not " + "see for itself, which is what the registry exists to deliver."); } private static IEnumerator OnServerReceive(long sender, ZPackage package) { if (package != null && package.Size() > 0) { Logger.LogWarning((object)$"Discarding an unexpected portal-registry package from peer {sender}."); } yield break; } private static void Apply(List records) { if (StaveboundConfig.LogNetworkSync != null && StaveboundConfig.LogNetworkSync.Value) { SyncLog.Say($"Registry {Ordered.Count} -> {records.Count} portal(s): " + SyncLog.Difference(Ordered, records)); } Ordered.Clear(); ByPid.Clear(); foreach (PortalRecord record in records) { Ordered.Add(record); ByPid[record.Pid] = record; } } } internal sealed class PortalRegistryCommand : StaveboundCommand { public override string Name => "stave_portals"; public override string Help => "List the portals Stavebound knows about, nearest first. On a client this is what the server has told you, not what is loaded around you."; protected override void Execute(string[] args, Terminal context) { //IL_0077: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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) IReadOnlyList all = PortalRegistry.All; string text = (((Object)(object)ZNet.instance == (Object)null) ? "no world loaded" : (ZNet.instance.IsServer() ? "server" : "client")); if (all.Count == 0) { StaveboundCommand.Echo(context, "Stavebound (" + text + "): no portals known."); return; } Vector3 from = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); StaveboundCommand.Echo(context, $"Stavebound ({text}): {all.Count} portal(s)."); foreach (PortalRecord item in all.OrderBy((PortalRecord p) => Vector3.Distance(from, p.Position))) { PortalRecord record; string arg = ((item.TargetPid == 0L) ? "vanilla tag pairing" : (PortalRegistry.TryGet(item.TargetPid, out record) ? record.ToString() : "a portal that no longer exists")); StaveboundCommand.Echo(context, $" {item} at {item.Position.x:F0},{item.Position.z:F0} " + $"({Vector3.Distance(from, item.Position):F0}m) " + $"-> {arg}, clearance 0x{item.ClearanceMask:X}"); } } } internal static class PortalTarget { private static readonly FieldRef NetView = AccessTools.FieldRefAccess("m_nview"); internal const long NoPid = 0L; internal static long GetPid(ZDO zdo) { if (zdo != null) { return zdo.GetLong(ZdoKeys.Pid, 0L); } return 0L; } internal static long GetDestination(ZDO zdo) { if (zdo != null) { return zdo.GetLong(ZdoKeys.Destination, 0L); } return 0L; } internal static long GetDestination(TeleportWorld portal) { return GetDestination(ZdoOf(portal)); } internal static ZDO ZdoOf(TeleportWorld portal) { if ((Object)(object)portal == (Object)null) { return null; } ZNetView val = NetView.Invoke(portal); if (!((Object)(object)val != (Object)null) || !val.IsValid()) { return null; } return val.GetZDO(); } internal static long EnsurePid(ZDO zdo, Func isTaken) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return 0L; } long num = GetPid(zdo); ZDOID zDOID = zdo.GetZDOID(ZdoKeys.LegacyTarget); bool flag = !((ZDOID)(ref zDOID)).IsNone(); bool flag2 = num == 0L || isTaken(num); if (!flag2 && !flag) { return num; } Claim(zdo); if (flag2) { num = Mint(); zdo.Set(ZdoKeys.Pid, num); } if (flag) { zdo.RemoveZDOID(ZdoKeys.LegacyTarget); } Publish(zdo); return num; } internal static void Set(ZDO zdo, long destinationPid) { if (zdo != null && destinationPid != 0L) { Claim(zdo); zdo.Set(ZdoKeys.Destination, destinationPid); Publish(zdo); } } internal static void Clear(ZDO zdo) { if (zdo != null) { Claim(zdo); zdo.Set(ZdoKeys.Destination, 0L); Publish(zdo); } } private static long Mint() { long num = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); if (num != 0L) { return num; } return 1L; } internal static void Claim(ZDO zdo) { if (!zdo.IsOwner()) { zdo.SetOwner(ZDOMan.GetSessionID()); } } internal static void Publish(ZDO zdo) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(zdo.m_uid); } } internal static TeleportWorld FindNearest(Vector3 point, float range) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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) TeleportWorld result = null; float num = range * range; TeleportWorld[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (TeleportWorld val in array) { if (ZdoOf(val) != null) { Vector3 val2 = ((Component)val).transform.position - point; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (!(sqrMagnitude > num)) { num = sqrMagnitude; result = val; } } } return result; } } internal static class ReaimGuard { internal static bool MayReaim(Vector3 position, out string refusal) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) refusal = null; switch (StaveboundConfig.Reaim.Value) { case ReaimPermission.Admin: if (!SynchronizationManager.Instance.PlayerIsAdmin) { refusal = Translations.Get("stave_reaim_admin_only"); return false; } return true; case ReaimPermission.GuardStonePermitted: if (!PrivateArea.CheckAccess(position, 0f, true, false)) { refusal = "$piece_noaccess"; return false; } return true; default: return true; } } } internal abstract class StaveboundCommand : ConsoleCommand { public sealed override void Run(string[] args) { ((ConsoleCommand)this).Run(args, (Terminal)(object)Console.instance); } public sealed override void Run(string[] args, Terminal context) { if ((Object)(object)context == (Object)null) { return; } try { Execute(args ?? Array.Empty(), context); } catch (Exception ex) { context.AddString("Stavebound: " + ((ConsoleCommand)this).Name + " failed - " + ex.GetType().Name + ": " + ex.Message); Logger.LogError((object)$"{((ConsoleCommand)this).Name} threw: {ex}"); } } protected abstract void Execute(string[] args, Terminal context); protected static void Echo(Terminal context, string line) { context.AddString(line); Logger.LogInfo((object)line); } } internal static class SyncLog { private static bool Enabled => StaveboundConfig.LogNetworkSync?.Value ?? false; internal static void Say(string message) { if (Enabled) { Logger.LogInfo((object)("[sync] " + message)); } } internal static void Warn(string message) { Logger.LogWarning((object)("[sync] " + message)); } internal static string Difference(IReadOnlyList before, IReadOnlyList after) { Dictionary dictionary = new Dictionary(); foreach (PortalRecord item in before) { dictionary[item.Pid] = item; } List list = new List(); List list2 = new List(); List list3 = new List(); foreach (PortalRecord item2 in after) { if (!dictionary.TryGetValue(item2.Pid, out var value)) { list.Add(item2.ToString()); continue; } if (value.TargetPid != item2.TargetPid) { list2.Add($"{item2} -> {Name(after, item2.TargetPid)}"); } if (value.Name != item2.Name) { list3.Add($"{value} is now {item2}"); } dictionary.Remove(item2.Pid); } List list4 = new List(); if (list.Count > 0) { list4.Add("added " + string.Join(", ", list)); } if (dictionary.Count > 0) { list4.Add("gone " + string.Join(", ", dictionary.Values.Select((PortalRecord p) => p.ToString()))); } if (list2.Count > 0) { list4.Add("re-aimed " + string.Join(", ", list2)); } if (list3.Count > 0) { list4.Add("renamed " + string.Join(", ", list3)); } if (list4.Count != 0) { return string.Join("; ", list4); } return "no visible difference"; } internal static string Name(IReadOnlyList among, long pid) { if (pid == 0L) { return "vanilla tag pairing"; } foreach (PortalRecord item in among) { if (item.Pid == pid) { return item.ToString(); } } return $"an unknown portal (pid {pid})"; } } internal static class ZdoKeys { internal static readonly int Pid = StringExtensionMethods.GetStableHashCode("stave_pid"); internal static readonly int Destination = StringExtensionMethods.GetStableHashCode("stave_dest"); internal static readonly KeyValuePair LegacyTarget = ZDO.GetHashZDOID("stave_target"); internal static readonly int ClearanceMask = StringExtensionMethods.GetStableHashCode("stave_mask"); } } namespace Stavebound.Patches { [HarmonyPatch(typeof(InventoryGrid))] internal static class CargoPreviewPatches { [HarmonyPostfix] [HarmonyPatch("UpdateGui")] private static void MarkWhatTheDestinationRefuses(List ___m_elements, Inventory ___m_inventory) { if (___m_elements == null || ___m_inventory == null || !CargoPreview.TryGetNearbyDestination(out var mask, out var allowsEverything)) { return; } foreach (Element ___m_element in ___m_elements) { if (!((Object)(object)___m_element?.m_noteleport == (Object)null) && ___m_element.m_used) { ItemData itemAt = ___m_inventory.GetItemAt(___m_element.m_pos.x, ___m_element.m_pos.y); ((Behaviour)___m_element.m_noteleport).enabled = CargoPreview.WouldBeRefused(itemAt, mask, allowsEverything); } } } } [HarmonyPatch(typeof(Game))] internal static class GameLifecyclePatches { [HarmonyPostfix] [HarmonyPatch("Start")] private static void StartRegistry() { PortalRegistry.OnWorldStart(); SiteSweep.Start(); } [HarmonyPrefix] [HarmonyPatch("OnDestroy")] private static void StopRegistry() { PortalRegistry.OnWorldEnd(); SiteSweep.Stop(); PlacementFeedback.Reset(); ApproachWarning.Reset(); DestinationSelector.Reset(); } } [HarmonyPatch(typeof(Minimap))] internal static class MinimapPatches { [HarmonyPostfix] [HarmonyPatch("Update")] private static void DriveSelector() { DestinationSelector.Update(); } [HarmonyPrefix] [HarmonyPatch("OnMapLeftClick")] private static bool ClickToHighlight(Minimap __instance) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!DestinationSelector.IsOpen) { return true; } DestinationSelector.HighlightNearest((Vector3)AccessTools.Method(typeof(Minimap), "ScreenToWorldPoint", (Type[])null, (Type[])null).Invoke(__instance, new object[1] { Input.mousePosition })); return false; } } [HarmonyPatch(typeof(ObjectDB))] internal static class ObjectDbPatches { [HarmonyPostfix] [HarmonyPatch("Awake")] private static void BuildOnAwake() { TierMap.Build(); } [HarmonyPostfix] [HarmonyPatch("CopyOtherDB")] private static void BuildOnCopy() { TierMap.Build(); } } [HarmonyPatch(typeof(Player))] internal static class PlacementPatches { [HarmonyPostfix] [HarmonyPatch("UpdatePlacementGhost")] private static void ShowStaveRange(GameObject ___m_placementGhost) { PlacementFeedback.Update(___m_placementGhost); } } [HarmonyPatch(typeof(TeleportWorld))] internal static class TeleportWorldPatches { [HarmonyPrefix] [HarmonyPatch("Interact")] private static bool InteractReaims(TeleportWorld __instance, Humanoid human, bool alt, ref bool __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __result = true; if (!PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, true, false)) { if (human != null) { ((Character)human).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null); } return false; } if (alt) { TextInput.instance.RequestText((TextReceiver)(object)__instance, "$piece_portal_tag", 10); return false; } DestinationSelector.Open(__instance, human); return false; } [HarmonyPrefix] [HarmonyPatch("Teleport")] private static bool TravelToOurTarget(TeleportWorld __instance, Player player, ZNetView ___m_nview) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid()) { return true; } ZDO zDO = ___m_nview.GetZDO(); long destination = PortalTarget.GetDestination(zDO); if (!ClearanceGate.TryResolveDestination(zDO, out var destination2, out var pendingPid)) { if (destination != 0L) { ((Character)player).Message((MessageType)2, Translations.Get("stave_target_gone"), 0, (Sprite)null); return false; } return true; } ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance != (Object)null) { if (instance.GetGlobalKey((GlobalKeys)27)) { ((Character)player).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null); return false; } if (instance.GetGlobalKey((GlobalKeys)28) && IsBossActive(instance)) { ((Character)player).Message((MessageType)2, "$msg_blockedbyboss", 0, (Sprite)null); return false; } } if (destination2 == null) { if (pendingPid != 0L && PortalRegistry.TryGet(pendingPid, out var record)) { ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.RequestZDO(record.Id); } } ((Character)player).Message((MessageType)2, Translations.Get("stave_far_side_waiting"), 0, (Sprite)null); return false; } if (!__instance.m_allowAllItems) { ClearanceGate.Refusal refusal = ClearanceGate.FirstRefusal(((Humanoid)player).GetInventory(), ClearanceGate.EffectiveMask(zDO, destination2)); if (refusal != null) { ((Character)player).Message((MessageType)2, ClearanceGate.Explain(refusal, destination2.GetString(ZDOVars.s_tag, string.Empty)), 0, (Sprite)null); return false; } } Vector3 position = destination2.GetPosition(); Quaternion rotation = destination2.GetRotation(); Vector3 val = position + rotation * Vector3.forward * __instance.m_exitDistance + Vector3.up; bool flag = SeamlessTransit.NeedsLoadingScreen(val); ((Character)player).TeleportTo(val, rotation, flag); SeamlessTransit.ShortenPause(player, val); Game instance3 = Game.instance; if (instance3 != null) { instance3.IncrementPlayerStat((PlayerStatType)15, 1f); } return false; } private static bool IsBossActive(ZoneSystem zones) { if ((Object)(object)RandEventSystem.instance != (Object)null && RandEventSystem.instance.GetBossEvent() != null) { return true; } float num = default(float); if (zones.GetGlobalKey((GlobalKeys)38, ref num)) { return num > 0f; } return false; } [HarmonyPostfix] [HarmonyPatch("GetHoverText")] private static void DescribeOurKeys(ref string __result) { if (string.IsNullOrEmpty(__result)) { return; } string text = Localization.instance.Localize("$piece_portal_settag"); if (__result.Contains(text)) { string text2 = Localization.instance.Localize("$KEY_Use"); ZInput instance = ZInput.instance; string text3 = ((instance != null) ? instance.GetBoundKeyString("AltPlace", true) : null); if (string.IsNullOrEmpty(text3)) { text3 = Localization.instance.Localize("$KEY_AltPlace"); } __result = __result.Replace(text, Translations.Get("stave_hover_aim")) + "\n[" + text3 + "+" + text2 + "] " + text; } } [HarmonyPostfix] [HarmonyPatch("UpdatePortal")] private static void GlowForThisPlayersCargo(TeleportWorld __instance, ZNetView ___m_nview) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance.m_target_found == (Object)null || (Object)(object)__instance.m_proximityRoot == (Object)null || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid()) { return; } Player closestPlayer = Player.GetClosestPlayer(__instance.m_proximityRoot.position, __instance.m_activationRange); if (!((Object)(object)closestPlayer == (Object)null)) { ZDO zDO = ___m_nview.GetZDO(); if (ClearanceGate.TryResolveDestination(zDO, out var destination, out var _) && destination != null) { bool flag = ClearanceGate.Allows(closestPlayer, zDO, destination, __instance.m_allowAllItems); __instance.m_target_found.SetActive(flag); ApproachWarning.Consider(___m_nview.GetZDO(), destination, closestPlayer, flag); } } } [HarmonyPostfix] [HarmonyPatch("HaveTarget")] private static void HaveOurTarget(ref bool __result, ZNetView ___m_nview) { if (!__result && !((Object)(object)___m_nview == (Object)null) && ___m_nview.IsValid()) { __result = PortalTarget.GetDestination(___m_nview.GetZDO()) != 0; } } [HarmonyPostfix] [HarmonyPatch("TargetFound")] private static void OurTargetFound(ref bool __result, ZNetView ___m_nview) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (__result || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid()) { return; } long destination = PortalTarget.GetDestination(___m_nview.GetZDO()); if (destination == 0L || !PortalRegistry.TryGet(destination, out var record)) { return; } ZDOMan instance = ZDOMan.instance; if (((instance != null) ? instance.GetZDO(record.Id) : null) != null) { __result = true; return; } ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.RequestZDO(record.Id); } } } [HarmonyPatch(typeof(Player))] internal static class TransitPatches { [HarmonyPrefix] [HarmonyPatch("UpdateTeleport")] private static void EndTheWaitWhenTheWorldIsThere(bool ___m_teleporting, ref bool ___m_distantTeleport, Vector3 ___m_teleportTargetPos) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (___m_teleporting && ___m_distantTeleport && SeamlessTransit.ArrivalIsReady(___m_teleportTargetPos)) { ___m_distantTeleport = false; } } } } namespace Stavebound.Config { internal static class SelectorKeys { private const string Section = "5 - Selector keys"; private static readonly Dictionary> BoundKeys = new Dictionary>(); internal const string Confirm = "stave_confirm"; internal const string Cancel = "stave_cancel"; internal const string Next = "stave_next"; internal const string Previous = "stave_previous"; internal const string Sort = "stave_sort"; internal const string Filter = "stave_filter"; internal static string KeyLabel(string button) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!BoundKeys.TryGetValue(button, out var value)) { return button; } return ((object)value.Value/*cast due to .constrained prefix*/).ToString(); } internal static bool Pressed(string button) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (BoundKeys.TryGetValue(button, out var value) && (int)value.Value != 0 && Input.GetKeyDown(value.Value)) { return true; } return ZInput.GetButtonDown(button); } internal static void Bind(ConfigFile config) { Register(config, "stave_confirm", "Confirm the highlighted destination.", (KeyCode)112, (GamepadButton)5); Register(config, "stave_cancel", "Close the selector without changing anything.", (KeyCode)27, (GamepadButton)6); Register(config, "stave_next", "Highlight the next destination.", (KeyCode)275, (GamepadButton)10); Register(config, "stave_previous", "Highlight the previous destination.", (KeyCode)276, (GamepadButton)9); Register(config, "stave_sort", "Cycle how the destination list is ordered.", (KeyCode)111, (GamepadButton)7); Register(config, "stave_filter", "Show only destinations that will accept what you are carrying.", (KeyCode)107, (GamepadButton)8); } private static void Register(ConfigFile config, string name, string description, KeyCode defaultKey, GamepadButton defaultPad) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0035: 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_0052: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown ConfigEntry val = config.Bind("5 - Selector keys", name, defaultKey, new ConfigDescription(description + " Local to you.", (AcceptableValueBase)null, Array.Empty())); ConfigEntry gamepadConfig = config.Bind("5 - Selector keys", name + "_gamepad", defaultPad, new ConfigDescription(description + " Gamepad. Local to you.", (AcceptableValueBase)null, Array.Empty())); BoundKeys[name] = val; InputManager.Instance.AddButton("com.recognizerhd.stavebound", new ButtonConfig { Name = name, Key = defaultKey, GamepadButton = defaultPad, Config = val, GamepadConfig = gamepadConfig, ActiveInGUI = true, ActiveInCustomGUI = true, BlockOtherInputs = true }); } } internal enum PortalBinding { Nearest, AllInRadius } internal enum ReaimPermission { Anyone, GuardStonePermitted, Admin } internal enum MaterialFlow { Receive, Deliver, Both } internal static class StaveboundConfig { private const string SectionTravel = "1 - Travel"; private const string SectionClearance = "2 - Clearance"; private const string SectionCargoPreview = "3 - Cargo preview"; private const string SectionCompatibility = "4 - Compatibility"; private const string SectionTransit = "6 - Transit"; private const string SectionDiagnostics = "7 - Diagnostics"; internal static ConfigEntry HidePortalNames { get; private set; } internal static ConfigEntry Reaim { get; private set; } internal static ConfigEntry StrictLadder { get; private set; } internal static ConfigEntry StaveRadius { get; private set; } internal static ConfigEntry Binding { get; private set; } internal static ConfigEntry Flow { get; private set; } internal static ConfigEntry ElderItems { get; private set; } internal static ConfigEntry BonemassItems { get; private set; } internal static ConfigEntry ModerItems { get; private set; } internal static ConfigEntry YagluthItems { get; private set; } internal static ConfigEntry QueenItems { get; private set; } internal static ConfigEntry AshenItems { get; private set; } internal static ConfigEntry ShowBlockedCargoOverlay { get; private set; } internal static ConfigEntry WarnOnApproach { get; private set; } internal static ConfigEntry CargoPreviewRange { get; private set; } internal static ConfigEntry SeamlessTransit { get; private set; } internal static ConfigEntry TransitPause { get; private set; } internal static ConfigEntry WarnOnConflictingMods { get; private set; } internal static ConfigEntry IgnoredConflictGuids { get; private set; } internal static ConfigEntry LogNetworkSync { get; private set; } internal static IEnumerable> TierPrefabs() { yield return new KeyValuePair(Clearance.Elder, ElderItems.Value); yield return new KeyValuePair(Clearance.Bonemass, BonemassItems.Value); yield return new KeyValuePair(Clearance.Moder, ModerItems.Value); yield return new KeyValuePair(Clearance.Yagluth, YagluthItems.Value); yield return new KeyValuePair(Clearance.Queen, QueenItems.Value); yield return new KeyValuePair(Clearance.Ashen, AshenItems.Value); } internal static void Bind(ConfigFile config) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Expected O, but got Unknown //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Expected O, but got Unknown //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Expected O, but got Unknown //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Expected O, but got Unknown //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Expected O, but got Unknown //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Expected O, but got Unknown HidePortalNames = config.Bind("1 - Travel", "HidePortalNames", false, new ConfigDescription("Hide portal names in the map selector. Local to you.", (AcceptableValueBase)null, Array.Empty())); Reaim = config.Bind("1 - Travel", "ReaimPermission", ReaimPermission.Anyone, Synced("Who may change where a portal points. Anyone: no restriction. GuardStonePermitted: inside a guard stone's area, only the players it permits. Admin: admins only.")); StrictLadder = config.Bind("2 - Clearance", "StrictLadder", false, Synced("A site's clearance stops at its first missing rung: with Elder's and Moder's but no Bonemass's, it accepts copper and refuses silver as well as iron. Off by default, and off is the shipped rule - per-tier flags are independent (R1), so a site can accept silver while refusing iron. Nothing stops you building any rune either way; this only changes what the ones you built are worth.")); StaveRadius = config.Bind("2 - Clearance", "StaveRadius", 10f, Synced("How far a stave reaches to find the portal it grants clearance to (R2). A rune outside every portal's reach does nothing at all.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 64f))); Binding = config.Bind("2 - Clearance", "PortalBinding", PortalBinding.Nearest, Synced("Nearest: a stave grants its clearance to the single closest portal in range. Re-aiming reaches everywhere from one portal, so a site only needs one. AllInRadius: every portal in range, for a base spread across more than one.")); Flow = config.Bind("2 - Clearance", "MaterialFlow", MaterialFlow.Both, Synced("Which end of a trip a site's staves count for. Receive: only the destination is checked, so a site takes what its staves permit from anywhere but cannot send those metals somewhere that lacks them - ore flows inward and outposts are one-way. Deliver: the mirror image, only the portal you leave is checked, so a stocked base can supply bare outposts but they cannot ship home. Both (default): either end is enough, and only two sites that both lack a tier cannot pass it between them.")); ElderItems = config.Bind("2 - Clearance", "ElderItems", "CopperOre,Copper,TinOre,Tin,Bronze,CopperScrap,BronzeScrap,chest_hildir3", Synced("Blocked items an Elder's Stave permits. Comma-separated prefab names.")); BonemassItems = config.Bind("2 - Clearance", "BonemassItems", "IronScrap,Iron,IronOre,Ironpit", Synced("Blocked items a Bonemass's Stave permits. Comma-separated prefab names.")); ModerItems = config.Bind("2 - Clearance", "ModerItems", "SilverOre,Silver,DragonEgg,chest_hildir2", Synced("Blocked items a Moder's Stave permits. Comma-separated prefab names.")); YagluthItems = config.Bind("2 - Clearance", "YagluthItems", "BlackMetalScrap,BlackMetal,chest_hildir1", Synced("Blocked items a Yagluth's Stave permits. Comma-separated prefab names.")); QueenItems = config.Bind("2 - Clearance", "QueenItems", "DvergrNeedle,MechanicalSpring", Synced("Blocked items a Queen's Stave permits. Comma-separated prefab names. The Mistlands does block resources, which DESIGN.md §4 originally assumed it did not - the ObjectDB scan is what settled it.")); AshenItems = config.Bind("2 - Clearance", "AshenItems", "FlametalOre,Flametal,FlametalOreNew,FlametalNew,CharredCogwheel", Synced("Blocked items an Ashen Stave permits. Comma-separated prefab names. Anything blocked and unlisted lands here anyway, by design.")); ShowBlockedCargoOverlay = config.Bind("3 - Cargo preview", "ShowBlockedCargoOverlay", true, new ConfigDescription("Mark inventory stacks the nearby portal's destination will refuse. Purely visual — it reads the tier map, never item data. Local to you.", (AcceptableValueBase)null, Array.Empty())); WarnOnApproach = config.Bind("3 - Cargo preview", "WarnOnApproach", true, new ConfigDescription("Name the offending item and the missing stave as you walk up to a portal whose destination would refuse you, rather than at the threshold. The portal's runes go dark either way. Local to you.", (AcceptableValueBase)null, Array.Empty())); CargoPreviewRange = config.Bind("3 - Cargo preview", "CargoPreviewRange", 8f, new ConfigDescription("How close to a portal the overlay switches on. Kept short on purpose: showing it everywhere would paint your ore red all game and teach you to ignore it. Local to you.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 32f), Array.Empty())); SeamlessTransit = config.Bind("6 - Transit", "SeamlessTransit", false, new ConfigDescription("End a portal trip when the destination has actually loaded, rather than on vanilla's eight-second timer. A destination already in memory skips the loading screen entirely; one that is not shows it for exactly as long as loading takes. Loads nothing early and waits for the same condition vanilla does. Local to you.", (AcceptableValueBase)null, Array.Empty())); TransitPause = config.Bind("6 - Transit", "TransitPause", 0.5f, new ConfigDescription("Seconds to hold before moving you on a trip that needs no loading, in place of vanilla's two. Kept rather than removed because arriving in another biome with no beat at all is disorienting. Local to you.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); WarnOnConflictingMods = config.Bind("4 - Compatibility", "WarnOnConflictingMods", true, new ConfigDescription("Log a warning at startup when another installed mod also rewrites portal or teleport rules. Local to you.", (AcceptableValueBase)null, Array.Empty())); IgnoredConflictGuids = config.Bind("4 - Compatibility", "IgnoredConflictGuids", string.Empty, new ConfigDescription("Comma-separated plugin GUIDs to leave out of the conflict warning, for when the check flags something harmless. Local to you.", (AcceptableValueBase)null, Array.Empty())); LogNetworkSync = config.Bind("7 - Diagnostics", "LogNetworkSync", false, new ConfigDescription("Log every step of the portal registry's sync - sweeps, broadcasts, joins and receives - so a multiplayer problem can be read off one log instead of reproduced. Local to you. Off by default now that clearance has been confirmed across a real network; turn it on before reporting anything about portals not agreeing between machines, because the first question will be what this says.", (AcceptableValueBase)null, Array.Empty())); } private static ConfigDescription Synced(string description, AcceptableValueBase acceptableValues = null) { //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) //IL_0017: Expected O, but got Unknown //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown return new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } }); } } internal static class Translations { internal const string Refusal = "stave_refusal"; internal const string RefusalMore = "stave_refusal_more"; internal const string ThatPortal = "stave_that_portal"; internal const string TargetGone = "stave_target_gone"; internal const string FarSideWaiting = "stave_far_side_waiting"; internal const string ReaimAdminOnly = "stave_reaim_admin_only"; internal const string HoverAim = "stave_hover_aim"; internal const string SelectorTitle = "stave_sel_title"; internal const string SelectorByDistance = "stave_sel_by_distance"; internal const string SelectorByName = "stave_sel_by_name"; internal const string SelectorFiltered = "stave_sel_filtered"; internal const string SelectorFlowBoth = "stave_sel_flow_both"; internal const string SelectorFlowDeliver = "stave_sel_flow_deliver"; internal const string SelectorCarryingNothing = "stave_sel_carrying_nothing"; internal const string SelectorTakes = "stave_sel_takes"; internal const string SelectorRefuses = "stave_sel_refuses"; internal const string SelectorTally = "stave_sel_tally"; internal const string SelectorMoreAbove = "stave_sel_more_above"; internal const string SelectorMoreBelow = "stave_sel_more_below"; internal const string SelectorEmpty = "stave_sel_empty"; internal const string SelectorShowAll = "stave_sel_show_all"; internal const string SelectorNowhere = "stave_sel_nowhere"; internal const string SelectorAimed = "stave_sel_aimed"; internal const string Confirm = "stave_sel_confirm"; internal const string Cancel = "stave_sel_cancel"; internal const string Change = "stave_sel_change"; internal const string Sort = "stave_sel_sort"; internal const string Filter = "stave_sel_filter"; internal const string UnnamedPortal = "stave_unnamed_portal"; internal const string PortalAt = "stave_portal_at"; internal const string PlaceNoPortal = "stave_place_no_portal"; internal const string PlaceBinds = "stave_place_binds"; internal const string PlaceBindsAll = "stave_place_binds_all"; internal const string PlaceBindsNearest = "stave_place_binds_nearest"; internal const string AnUnnamedPortal = "stave_an_unnamed_portal"; internal const string RuneElder = "stave_rune_elder"; internal const string RuneBonemass = "stave_rune_bonemass"; internal const string RuneModer = "stave_rune_moder"; internal const string RuneYagluth = "stave_rune_yagluth"; internal const string RuneQueen = "stave_rune_queen"; internal const string RuneAshen = "stave_rune_ashen"; internal const string NoStave = "stave_rune_none"; internal const string DescElder = "stave_desc_elder"; internal const string DescBonemass = "stave_desc_bonemass"; internal const string DescModer = "stave_desc_moder"; internal const string DescYagluth = "stave_desc_yagluth"; internal const string DescQueen = "stave_desc_queen"; internal const string DescAshen = "stave_desc_ashen"; private static readonly Dictionary English = new Dictionary { { "stave_refusal", "{0} cannot enter {1} — no {2} there." }, { "stave_refusal_more", " (and {0} more.)" }, { "stave_that_portal", "that portal" }, { "stave_target_gone", "This portal points at somewhere that no longer exists." }, { "stave_far_side_waiting", "The far side has not answered yet." }, { "stave_reaim_admin_only", "Only an admin may re-aim portals on this server." }, { "stave_hover_aim", "Aim portal" }, { "stave_sel_title", "Aim {0} at" }, { "stave_sel_by_distance", "by distance" }, { "stave_sel_by_name", "by name" }, { "stave_sel_filtered", ", only what takes my load" }, { "stave_sel_flow_both", "either end counts - this portal's staves travel with you" }, { "stave_sel_flow_deliver", "only this portal's staves count, not the destination's" }, { "stave_sel_carrying_nothing", "Carrying nothing a portal would refuse." }, { "stave_sel_takes", "This one takes your load." }, { "stave_sel_refuses", "This one would refuse you." }, { "stave_sel_tally", "{0} of {1} take your load." }, { "stave_sel_more_above", "{0} more above" }, { "stave_sel_more_below", "{0} more below" }, { "stave_sel_empty", "Nothing here will take what you are carrying." }, { "stave_sel_show_all", "show everything" }, { "stave_sel_nowhere", "There is nowhere else to point this portal." }, { "stave_sel_aimed", "{0} now points at {1}." }, { "stave_sel_confirm", "confirm" }, { "stave_sel_cancel", "cancel" }, { "stave_sel_change", "change" }, { "stave_sel_sort", "sort" }, { "stave_sel_filter", "filter" }, { "stave_unnamed_portal", "unnamed portal" }, { "stave_portal_at", "portal at {0}, {1}" }, { "stave_place_no_portal", "No portal in range — this stave would do nothing here." }, { "stave_place_binds", "Binds to {0}." }, { "stave_place_binds_all", "Binds to all {0} portals in range." }, { "stave_place_binds_nearest", "Binds to {0}, the nearest of {1} in range." }, { "stave_an_unnamed_portal", "an unnamed portal" }, { "stave_rune_elder", "Elder's Stave" }, { "stave_rune_bonemass", "Bonemass's Stave" }, { "stave_rune_moder", "Moder's Stave" }, { "stave_rune_yagluth", "Yagluth's Stave" }, { "stave_rune_queen", "Queen's Stave" }, { "stave_rune_ashen", "Ashen Stave" }, { "stave_rune_none", "no stave" }, { "stave_desc_elder", "Lets copper, tin and bronze arrive at this site." }, { "stave_desc_bonemass", "Lets iron arrive at this site." }, { "stave_desc_moder", "Lets silver and dragon eggs arrive at this site." }, { "stave_desc_yagluth", "Lets black metal arrive at this site." }, { "stave_desc_queen", "Lets the Mistlands' guarded things arrive at this site." }, { "stave_desc_ashen", "Lets flametal and the Ashlands' spoils arrive at this site." } }; internal static void Add() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); foreach (KeyValuePair item in English) { string text = "English"; string key = item.Key; localization.AddTranslation(ref text, ref key, item.Value); } } internal static string Get(string token) { if (Localization.instance == null) { if (!English.TryGetValue(token, out var value)) { return token; } return value; } return Localization.instance.Localize("$" + token); } internal static string Format(string token, params object[] args) { string text = Get(token); try { return string.Format(text, args); } catch (FormatException) { Logger.LogWarning((object)("Translation '" + token + "' has a malformed placeholder and was skipped: " + text)); string value; return English.TryGetValue(token, out value) ? string.Format(value, args) : text; } } } } namespace Stavebound.Compat { internal static class ConflictDetector { private static readonly Dictionary KnownConflicts = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["org.bepinex.plugins.valheim_plus"] = "rewrites teleport rules wholesale, including what may be carried through a portal", ["SpikeHimself.XPortal"] = "owns the portal destination list, which Stavebound also provides" }; private static readonly string[] SuspectKeywords = new string[2] { "portal", "teleport" }; internal static void WarnAboutKnownConflicts() { if (!StaveboundConfig.WarnOnConflictingMods.Value) { return; } HashSet hashSet = ParseIgnoreList(StaveboundConfig.IgnoredConflictGuids.Value); List list = new List(); List list2 = new List(); foreach (PluginInfo value2 in Chainloader.PluginInfos.Values) { BepInPlugin val = ((value2 != null) ? value2.Metadata : null); if (val != null && !(val.GUID == "com.recognizerhd.stavebound") && !hashSet.Contains(val.GUID)) { if (KnownConflicts.TryGetValue(val.GUID, out var value)) { list.Add(val.Name + " (" + val.GUID + ") — " + value); } else if (LooksLikeAPortalMod(val.GUID) || LooksLikeAPortalMod(val.Name)) { list2.Add(val.Name + " (" + val.GUID + ")"); } } } if (list.Count > 0) { Logger.LogWarning((object)string.Format("{0} found {1} installed mod(s) that conflict with it:", "Stavebound", list.Count)); foreach (string item in list) { Logger.LogWarning((object)(" - " + item)); } Logger.LogWarning((object)"Expect portal behaviour neither mod intends. Remove one of them before reporting bugs."); } if (list2.Count > 0) { Logger.LogWarning((object)(string.Format("{0} also sees {1} mod(s) that look portal-related and may ", "Stavebound", list2.Count) + "clash: " + string.Join(", ", list2.ToArray()))); Logger.LogWarning((object)"If one of those is harmless, add its GUID to IgnoredConflictGuids to silence this."); } } private static bool LooksLikeAPortalMod(string value) { if (!string.IsNullOrEmpty(value)) { return SuspectKeywords.Any((string keyword) => value.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0); } return false; } private static HashSet ParseIgnoreList(string raw) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(raw)) { return hashSet; } string[] array = raw.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } } }