Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Stavebound v0.9.0
plugins\Stavebound.dll
Decompiled 11 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.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<PortalRecord> Candidates = new List<PortalRecord>(); private static readonly List<PinData> Pins = new List<PinData>(); 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("<color=#E06C4A>" + Translations.Get("stave_sel_empty") + "</color>"); stringBuilder.AppendLine(); stringBuilder.Append("<size=13>[" + Bound("stave_filter") + "] " + Translations.Get("stave_sel_show_all") + " [" + Bound("stave_cancel") + "] " + Translations.Get("stave_sel_cancel") + "</size>"); 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("<size=13>" + text + text2 + "</size>"); string text3 = FlowNote(); if (text3 != null) { stringBuilder2.AppendLine("<size=13><color=#B9A67A>" + text3 + "</color></size>"); } 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) ? ("<size=13>" + Translations.Format("stave_sel_more_above", num) + "</size>") : " "); for (int i = num; i < num2; i++) { PortalRecord portal = Candidates[i]; float num3 = Vector3.Distance(_sourcePosition, portal.Position); string text4 = $"{Describe(portal)} <size=13>{num3:F0}m</size>\n {Chips(portal)}"; stringBuilder2.AppendLine((i == _highlight) ? ("<color=#FFB726>» " + text4 + "</color>") : ("<color=#C9C0AC> " + text4 + "</color>")); } stringBuilder2.AppendLine((num2 < Candidates.Count) ? ("<size=13>" + Translations.Format("stave_sel_more_below", Candidates.Count - num2) + "</size>") : " "); stringBuilder2.AppendLine(); stringBuilder2.AppendLine("<size=13>[" + Bound("stave_confirm") + "] " + Translations.Get("stave_sel_confirm") + " [" + Bound("stave_cancel") + "] " + Translations.Get("stave_sel_cancel") + "</size>"); stringBuilder2.Append("<size=13>[" + 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") + "</size>"); 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("<size=12>"); 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("<color=#E06C4A>" + clearance.Symbol() + "</color>"); } else { stringBuilder.Append("··"); } stringBuilder.Append(' '); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()) + "</size>"; } private static string Verdict(PortalRecord destination) { if (_carrying == Clearance.None) { return "<size=13>" + Translations.Get("stave_sel_carrying_nothing") + "</size>"; } int num = Candidates.Count(Accepts); string text = "<size=13>" + Translations.Format("stave_sel_tally", num, Candidates.Count) + "</size>"; if (!Accepts(destination)) { return "<size=13><color=#E06C4A>" + Translations.Get("stave_sel_refuses") + "</color></size> " + text; } return "<size=13><color=#8FC97A>" + Translations.Get("stave_sel_takes") + "</color></size> " + 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>(); _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<Player, float> TeleportTimer = AccessTools.FieldRefAccess<Player, float>("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<string> list = TierMap.AtTier(tier).Select(TierMap.Describe).ToList(); StaveboundCommand.Echo(context, $" {tier.StaveName()} ({list.Count}): " + ((list.Count == 0) ? "nothing" : string.Join(", ", list))); } IReadOnlyList<string> 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 <prefab> - 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<string> list = (from c in ((Component)node).GetComponents<Component>() 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<Renderer>(); 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<string> list = new List<string>(); 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<GameObject> Previews = new List<GameObject>(); public override string Name => "stave_preview"; public override string Help => "stave_preview <prefab> - 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<ZNetView>() != (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<GameObject>(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 <text>[,<text>...] - 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<GameObject> 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<string> list2 = new List<string>(); if ((Object)(object)item.GetComponent<Piece>() != (Object)null) { list2.Add("piece"); } ItemDrop component = item.GetComponent<ItemDrop>(); 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<string, Clearance> Required = new Dictionary<string, Clearance>(StringComparer.OrdinalIgnoreCase); private static readonly List<string> Unclassified = new List<string>(); private static readonly Dictionary<string, string> DisplayTokens = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); internal static bool Built { get; private set; } internal static int BlockedCount => Required.Count; internal static IReadOnlyList<string> 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<string> 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<string, Clearance> dictionary = ReadConfiguredTiers(); foreach (GameObject item in instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); 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<string, Clearance> ReadConfiguredTiers() { Dictionary<string, Clearance> dictionary = new Dictionary<string, Clearance>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<Clearance, string> 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<string> list = new List<string>(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(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<Light>(true); foreach (Light val4 in componentsInChildren2) { val4.color = tint; list.Add("light on " + ((Object)val4).name); } ParticleSystem[] componentsInChildren3 = prefab.GetComponentsInChildren<ParticleSystem>(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<TeleportWorld> InRange = new List<TeleportWorld>(); 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<TeleportWorld>((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<StationExtension>)Resources.FindObjectsOfTypeAll<StationExtension>()).FirstOrDefault((Func<StationExtension, bool>)((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<GameObject>(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<CircleProjector>)Resources.FindObjectsOfTypeAll<CircleProjector>()).FirstOrDefault((Func<CircleProjector, bool>)((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<CircleProjector>(); _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<ZDO> Found = new List<ZDO>(); private static readonly Dictionary<ZDO, Clearance> PortalMasks = new Dictionary<ZDO, Clearance>(); 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<string, Clearance> 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<ZDO>(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<ZDO> 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<ZDO> 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<ZDO> 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<string> list = new List<string>(); 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<PieceSpec> Specs = new List<PieceSpec> { 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<string, Clearance> Granted = new Dictionary<string, Clearance> { { "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<KeyValuePair<string, Clearance>> 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<Pickable>(); 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<Piece>() ?? prefab.AddComponent<Piece>(); 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<WearNTear>() != (Object)null)) { WearNTear obj2 = prefab.AddComponent<WearNTear>(); 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 <destination name> - 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<PortalRecord> 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<string> 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<ZNetPeer> list = ZNet.instance.GetConnectedPeers() ?? new List<ZNetPeer>(); 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<PortalRecord> 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<PortalRecord> list2 = all.Where((PortalRecord p) => p.TargetPid != 0).ToList(); PortalRecord record; List<PortalRecord> 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<PortalRecord> { 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<PortalRecord> Ordered = new List<PortalRecord>(); private static readonly Dictionary<long, PortalRecord> ByPid = new Dictionary<long, PortalRecord>(); private static readonly List<PortalRecord> SweepBuffer = new List<PortalRecord>(); private static readonly HashSet<long> SweepPids = new HashSet<long>(); 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<PortalRecord> 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<ZPackage>)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<ZDO> 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<PortalRecord> 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<ZNetPeer> 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<PortalRecord> list = new List<PortalRecord>(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<PortalRecord> 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<PortalRecord> 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<TeleportWorld, ZNetView> NetView = AccessTools.FieldRefAccess<TeleportWorld, ZNetView>("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<long, bool> 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<TeleportWorld>((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<string>(), 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<PortalRecord> before, IReadOnlyList<PortalRecord> after) { Dictionary<long, PortalRecord> dictionary = new Dictionary<long, PortalRecord>(); foreach (PortalRecord item in before) { dictionary[item.Pid] = item; } List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); 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<string> list4 = new List<string>(); 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<PortalRecord> 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<int, int> 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<Element> ___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