Decompiled source of InterServerPortal v0.10.0

InterServerPortal.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using InterServerPortal.Core;
using InterServerPortal.Hub;
using InterServerPortal.Net;
using InterServerPortal.Policy;
using InterServerPortal.Portal;
using InterServerPortal.Security;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("InterServerPortal")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.10.0.0")]
[assembly: AssemblyInformationalVersion("0.10.0+b9780a9ceabe8f2fa64113112eb5b641e05eb82c")]
[assembly: AssemblyProduct("InterServerPortal")]
[assembly: AssemblyTitle("InterServerPortal")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.10.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace InterServerPortal
{
	[BepInPlugin("com.interserverportal", "InterServerPortal", "0.10.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.interserverportal";

		public const string PluginName = "InterServerPortal";

		public const string PluginVersion = "0.10.0";

		private Harmony _harmony;

		internal ConfigEntry<bool> DebugLogging;

		internal ConfigEntry<bool> RememberServerPassword;

		internal ConfigEntry<string> DiscordWebhookUrl;

		internal ConfigEntry<bool> TollEnabled;

		internal ConfigEntry<TollChargeMode> TollChargeMode;

		internal ConfigEntry<bool> TollRequireBossKey;

		internal ConfigEntry<string>[] TollRules;

		internal ConfigEntry<string> TollDefaultRule;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private static ConfigDescription ServerConfig(string description)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			return new ConfigDescription(description + " [Server authoritative — the server's value overrides clients.]", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} });
		}

		private void Awake()
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enable verbose debug logging for InterServerPortal.");
			RememberServerPassword = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RememberServerPassword", false, "Persist the origin server password so the return trip can auto-reconnect to password-protected servers. Stored in plain text — leave off unless you need it.");
			DiscordWebhookUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "WebhookUrl", "", "Discord webhook URL. When set, posts a message whenever a player steps through a portal into their own local world. Leave empty to disable.");
			BindTollConfig();
			_harmony = new Harmony("com.interserverportal");
			_harmony.PatchAll();
			Log.LogInfo((object)"InterServerPortal v0.10.0 loaded (Phase 10 — wood-portal carry toll).");
		}

		private void BindTollConfig()
		{
			TollEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Toll", "Enabled", false, ServerConfig("Allow non-teleportable items (ores, metals, dragon eggs, …) through a WOOD portal for a fee deposited in that portal's storage. Off = vanilla behaviour (ore never teleports). Stone portals are unaffected — they already carry everything."));
			TollChargeMode = ((BaseUnityPlugin)this).Config.Bind<TollChargeMode>("Toll", "ChargeMode", InterServerPortal.Policy.TollChargeMode.PerStack, ServerConfig("PerStack = one fee per inventory stack of restricted cargo (forgiving). PerItem = one fee per individual unit carried (expensive for big hauls)."));
			TollRequireBossKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Toll", "RequireBossKey", true, ServerConfig("Also require the global key for the cargo's source-biome boss (e.g. copper/tin/bronze need the Elder). Turn off to charge the fee only."));
			TollRules = new ConfigEntry<string>[9]
			{
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "01_CopperTinBronze", "items=CopperOre,Copper,CopperScrap,TinOre,Tin,Bronze,BronzeScrap | fee=SurtlingCore:1 | key=defeated_gdking", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "02_Iron", "items=IronOre,Iron,IronScrap | fee=SurtlingCore:1 | key=defeated_bonemass", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "03_Silver", "items=SilverOre,Silver | fee=SurtlingCore:1 | key=defeated_dragon", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "04_BlackMetal", "items=BlackMetalScrap,BlackMetal | fee=SurtlingCore:1 | key=defeated_goblinking", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "05_Flametal", "items=FlametalOre,FlametalOreNew,Flametal,FlametalNew | fee=SurtlingCore:1 | key=defeated_fader", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "06_DragonEgg", "items=DragonEgg | fee=Crystal:1 | key=defeated_dragon", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>. Gated on Moder by design: the first summon never needs a portal, so this only affects re-summons, when Moder is already dead.")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "07_Mistlands", "items=MechanicalSpring,SapExtractor,DvergrExtractor | fee=Eitr:1 | key=defeated_queen", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>. 'Eitr' is the Refined eitr item. Verify the Dvergr extractor prefab name against your game version — an unresolved name simply falls through to the default rule.")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "08_Ashlands", "items=CharredCogwheel | fee=BlackCore:1 | key=defeated_fader", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>")),
				((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "09_Hildir", "items= | fee=Coins:20 | key=", ServerConfig("Toll rule — items=<prefabs> | fee=<prefab>:<amount> | key=<global key, blank for none>. Empty by default: most Hildir reward-chest loot is already teleportable, so add the prefab names you actually want taxed."))
			};
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				TollPolicy.InvalidateRules();
			};
			TollDefaultRule = ((BaseUnityPlugin)this).Config.Bind<string>("Toll.Rules", "99_Default", "items=* | fee=SurtlingCore:1 | key=", ServerConfig("Fallback for any non-teleportable item no rule above matched. Set fee= (blank) to let unmatched cargo through free, or delete items=* to block it."));
		}

		private void Update()
		{
			PortalData.UpdateArming();
			PortalNetwork.EnsureRegistered();
			PortalNetwork.Tick();
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			Instance = null;
		}

		internal static void Debug(string message)
		{
			if ((Object)(object)Instance != (Object)null && Instance.DebugLogging.Value)
			{
				Log.LogInfo((object)("[debug] " + message));
			}
		}
	}
}
namespace InterServerPortal.Security
{
	internal static class LockCodes
	{
		internal static string NewSalt()
		{
			byte[] array = new byte[16];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			return Convert.ToBase64String(array);
		}

		internal static string Hash(string salt, string code)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] bytes = Encoding.UTF8.GetBytes(salt + code);
			byte[] array = sHA.ComputeHash(bytes);
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		internal static bool Verify(string salt, string storedHash, string entered)
		{
			if (string.IsNullOrEmpty(storedHash))
			{
				return true;
			}
			return string.Equals(Hash(salt, entered), storedHash, StringComparison.Ordinal);
		}
	}
	internal static class LockGate
	{
		private class State
		{
			public int Fails;

			public float CooldownUntil;

			public float UnlockedUntil;
		}

		private const float UnlockWindow = 300f;

		private const float CooldownStep = 2f;

		private const float CooldownMax = 30f;

		private static readonly Dictionary<ZDOID, State> _states = new Dictionary<ZDOID, State>();

		private static bool TryKey(TeleportWorld portal, out ZDOID key)
		{
			//IL_0001: 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_0044: 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)
			key = ZDOID.None;
			if ((Object)(object)portal == (Object)null || (Object)(object)portal.m_nview == (Object)null || !portal.m_nview.IsValid())
			{
				return false;
			}
			ZDO zDO = portal.m_nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			key = zDO.m_uid;
			return true;
		}

		private static State Get(ZDOID key)
		{
			//IL_0005: 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)
			if (!_states.TryGetValue(key, out var value))
			{
				value = new State();
				_states[key] = value;
			}
			return value;
		}

		internal static bool IsUnlocked(TeleportWorld portal)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (!TryKey(portal, out var key))
			{
				return false;
			}
			if (_states.TryGetValue(key, out var value))
			{
				return Time.time < value.UnlockedUntil;
			}
			return false;
		}

		internal static void MarkUnlocked(TeleportWorld portal)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TryKey(portal, out var key))
			{
				State state = Get(key);
				state.UnlockedUntil = Time.time + 300f;
				state.Fails = 0;
				state.CooldownUntil = 0f;
			}
		}

		internal static bool InCooldown(TeleportWorld portal, out float remaining)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			remaining = 0f;
			if (!TryKey(portal, out var key))
			{
				return false;
			}
			if (!_states.TryGetValue(key, out var value))
			{
				return false;
			}
			remaining = value.CooldownUntil - Time.time;
			return remaining > 0f;
		}

		internal static void RegisterFail(TeleportWorld portal)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TryKey(portal, out var key))
			{
				State state = Get(key);
				state.Fails++;
				state.CooldownUntil = Time.time + Mathf.Min((float)state.Fails * 2f, 30f);
			}
		}
	}
}
namespace InterServerPortal.Portal
{
	internal class Destination
	{
		public string Label;

		public string WorldName;

		public string SpawnPointId = "default";

		private const string Version = "v1";

		public Destination()
		{
		}

		public Destination(string label, string worldName, string spawnPointId = "default")
		{
			WorldName = worldName;
			Label = (string.IsNullOrEmpty(label) ? worldName : label);
			SpawnPointId = (string.IsNullOrEmpty(spawnPointId) ? "default" : spawnPointId);
		}

		internal static string Serialize(List<Destination> dests)
		{
			StringBuilder stringBuilder = new StringBuilder("v1");
			if (dests != null)
			{
				foreach (Destination dest in dests)
				{
					if (dest != null && !string.IsNullOrEmpty(dest.WorldName))
					{
						stringBuilder.Append('|').Append(Encode(dest.Label)).Append(';')
							.Append(Encode(dest.WorldName))
							.Append(';')
							.Append(Encode(string.IsNullOrEmpty(dest.SpawnPointId) ? "default" : dest.SpawnPointId));
					}
				}
			}
			return stringBuilder.ToString();
		}

		internal static List<Destination> Deserialize(string raw)
		{
			List<Destination> list = new List<Destination>();
			if (string.IsNullOrEmpty(raw))
			{
				return list;
			}
			string[] array = raw.Split(new char[1] { '|' });
			if (array.Length == 0 || array[0] != "v1")
			{
				return list;
			}
			for (int i = 1; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ';' });
				if (array2.Length >= 2)
				{
					string text = Decode(array2[1]);
					if (!string.IsNullOrEmpty(text))
					{
						string label = Decode(array2[0]);
						string spawnPointId = ((array2.Length >= 3) ? Decode(array2[2]) : "default");
						list.Add(new Destination(label, text, spawnPointId));
					}
				}
			}
			return list;
		}

		private static string Encode(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			return s.Replace("%", "%25").Replace("|", "%7C").Replace(";", "%3B");
		}

		private static string Decode(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			return s.Replace("%3B", ";").Replace("%7C", "|").Replace("%25", "%");
		}
	}
	internal static class PortalData
	{
		private static readonly int EnabledHash = StringExtensionMethods.GetStableHashCode("ISP.enabled");

		private static readonly int DestsHash = StringExtensionMethods.GetStableHashCode("ISP.dests");

		private static readonly int CodeHashHash = StringExtensionMethods.GetStableHashCode("ISP.codehash");

		private static readonly int CodeSaltHash = StringExtensionMethods.GetStableHashCode("ISP.codesalt");

		private static readonly int LinkHash = StringExtensionMethods.GetStableHashCode("ISP.link");

		internal const int LinkTag = 0;

		internal const int LinkNetwork = 1;

		private static readonly HashSet<TeleportWorld> Portals = new HashSet<TeleportWorld>();

		private const float ReArmClearDistance = 3f;

		private static bool _teardownSeen;

		private static bool _prevAwaiting;

		internal static bool IsInterServer(ZNetView nview)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO != null)
			{
				return zDO.GetInt(EnabledHash, 0) != 0;
			}
			return false;
		}

		internal static bool IsInterServer(TeleportWorld portal)
		{
			if ((Object)(object)portal != (Object)null)
			{
				return IsInterServer(portal.m_nview);
			}
			return false;
		}

		internal static bool ToggleInterServer(ZNetView nview)
		{
			bool flag = !IsInterServer(nview);
			SetInterServer(nview, flag);
			return flag;
		}

		internal static void SetInterServer(ZNetView nview, bool enabled)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.ClaimOwnership();
				nview.GetZDO().Set(EnabledHash, enabled ? 1 : 0, false);
			}
		}

		internal static List<Destination> GetDestinations(ZNetView nview)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return new List<Destination>();
			}
			ZDO zDO = nview.GetZDO();
			return Destination.Deserialize((zDO != null) ? zDO.GetString(DestsHash, "") : "");
		}

		internal static void SetDestinations(ZNetView nview, List<Destination> dests)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.ClaimOwnership();
				nview.GetZDO().Set(DestsHash, Destination.Serialize(dests));
			}
		}

		internal static bool IsLocked(ZNetView nview)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO != null)
			{
				return !string.IsNullOrEmpty(zDO.GetString(CodeHashHash, ""));
			}
			return false;
		}

		internal static void SetCode(ZNetView nview, string code)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.ClaimOwnership();
				ZDO zDO = nview.GetZDO();
				if (string.IsNullOrEmpty(code))
				{
					zDO.Set(CodeHashHash, "");
					zDO.Set(CodeSaltHash, "");
				}
				else
				{
					string text = LockCodes.NewSalt();
					zDO.Set(CodeSaltHash, text);
					zDO.Set(CodeHashHash, LockCodes.Hash(text, code));
				}
			}
		}

		internal static bool CheckCode(ZNetView nview, string entered)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return true;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return true;
			}
			string text = zDO.GetString(CodeHashHash, "");
			if (string.IsNullOrEmpty(text))
			{
				return true;
			}
			return LockCodes.Verify(zDO.GetString(CodeSaltHash, ""), text, entered);
		}

		internal static int GetLinkMode(ZNetView nview)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return 0;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return 0;
			}
			return zDO.GetInt(LinkHash, 0);
		}

		internal static bool IsNetwork(ZNetView nview)
		{
			if (!IsInterServer(nview))
			{
				return GetLinkMode(nview) == 1;
			}
			return false;
		}

		internal static void SetLinkMode(ZNetView nview, int mode)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.ClaimOwnership();
				nview.GetZDO().Set(LinkHash, mode, false);
			}
		}

		internal static void Register(TeleportWorld portal)
		{
			if ((Object)(object)portal != (Object)null)
			{
				Portals.Add(portal);
			}
		}

		internal static void UpdateArming()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (WorldSwitcher.AwaitingArrival && !_prevAwaiting)
			{
				_teardownSeen = false;
			}
			_prevAwaiting = WorldSwitcher.AwaitingArrival;
			if (WorldSwitcher.AwaitingArrival)
			{
				if ((Object)(object)localPlayer == (Object)null)
				{
					_teardownSeen = true;
					return;
				}
				if (!_teardownSeen)
				{
					return;
				}
				WorldSwitcher.AwaitingArrival = false;
			}
			if (!WorldSwitcher.PortalArmed && !((Object)(object)localPlayer == (Object)null) && !AnyInterServerPortalWithin(((Component)localPlayer).transform.position, 3f))
			{
				WorldSwitcher.PortalArmed = true;
				Plugin.Debug("Portal switch re-armed — player clear of inter-server portals.");
			}
		}

		private static bool AnyInterServerPortalWithin(Vector3 pos, float range)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			Portals.RemoveWhere((TeleportWorld p) => (Object)(object)p == (Object)null);
			float num = range * range;
			foreach (TeleportWorld portal in Portals)
			{
				if (IsInterServer(portal.m_nview))
				{
					Vector3 val = ((Component)portal).transform.position - pos;
					if (((Vector3)(ref val)).sqrMagnitude <= num)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(TeleportWorld))]
	internal static class TeleportWorldPatches
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake_Postfix(TeleportWorld __instance)
		{
			PortalData.Register(__instance);
			PortalStorage.Attach(__instance);
		}

		[HarmonyPatch("Teleport")]
		[HarmonyPrefix]
		private static bool Teleport_Prefix(TeleportWorld __instance, Player player)
		{
			bool num = PortalData.IsInterServer(__instance.m_nview);
			bool flag = !num && PortalData.IsNetwork(__instance.m_nview);
			if (!num && !flag)
			{
				return VanillaTollGate(__instance, player);
			}
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (WorldSwitcher.InProgress)
			{
				return false;
			}
			if (flag)
			{
				if (!NetworkController.ArrivalSuppressed)
				{
					NetworkController.Begin(__instance);
				}
				return false;
			}
			if (!WorldSwitcher.PortalArmed)
			{
				Plugin.Debug("Portal trigger ignored — switching disarmed until player steps clear.");
				return false;
			}
			HubController.BeginTravel(__instance);
			return false;
		}

		private static bool VanillaTollGate(TeleportWorld portal, Player player)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: 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_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: 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_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if (!TollPolicy.Enabled)
			{
				return true;
			}
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (portal.m_allowAllItems)
			{
				return true;
			}
			if (((Humanoid)player).IsTeleportable())
			{
				return true;
			}
			if (!portal.TargetFound())
			{
				return true;
			}
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return true;
			}
			if (ZoneSystem.instance.GetGlobalKey((GlobalKeys)27))
			{
				return true;
			}
			if (BossPortalBlock())
			{
				return true;
			}
			if (!TollPolicy.Charge(portal, player))
			{
				return false;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(portal.m_nview.GetZDO().GetConnectionZDOID((ConnectionType)1));
			if (zDO != null)
			{
				Quaternion rotation = zDO.GetRotation();
				Vector3 val = zDO.GetPosition() + rotation * Vector3.forward * portal.m_exitDistance + Vector3.up;
				((Character)player).TeleportTo(val, rotation, true);
				Game.instance.IncrementPlayerStat((PlayerStatType)15, 1f);
				Plugin.Debug("Toll-paid vanilla portal hop completed.");
			}
			return false;
		}

		private static bool BossPortalBlock()
		{
			if (!ZoneSystem.instance.GetGlobalKey((GlobalKeys)28))
			{
				return false;
			}
			if ((Object)(object)RandEventSystem.instance != (Object)null && RandEventSystem.instance.GetBossEvent() != null)
			{
				return true;
			}
			float num = default(float);
			if (ZoneSystem.instance.GetGlobalKey((GlobalKeys)38, ref num))
			{
				return num > 0f;
			}
			return false;
		}

		[HarmonyPatch("Interact")]
		[HarmonyPrefix]
		private static bool Interact_Prefix(TeleportWorld __instance, Humanoid human, bool hold, bool alt, ref bool __result)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (hold || !alt)
			{
				return true;
			}
			if (!PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, true, false))
			{
				((Character)human).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null);
				__result = true;
				return false;
			}
			PortalConfigPanel.Show(__instance);
			__result = true;
			return false;
		}

		[HarmonyPatch("GetHoverText")]
		[HarmonyPostfix]
		private static void GetHoverText_Postfix(TeleportWorld __instance, ref string __result)
		{
			bool num = PortalData.IsInterServer(__instance.m_nview);
			bool flag = PortalData.IsNetwork(__instance.m_nview);
			string text = (num ? "InterServerPortal: <color=#7dd3fc>ON</color>" : ((!flag) ? "InterServerPortal: <color=grey>off</color>" : "InterServerPortal: <color=#c4b5fd>NETWORK</color>"));
			if ((num || flag) && PortalData.IsLocked(__instance.m_nview))
			{
				text += "  <color=#ffb0b0>[locked]</color>";
			}
			string text2 = (num ? "walk through to travel   [<color=yellow><b>L.Shift + $KEY_Use</b></color>] configure" : ((!flag) ? "[<color=yellow><b>L.Shift + $KEY_Use</b></color>] configure (InterServerPortal)" : "walk through to travel your portal network   [<color=yellow><b>L.Shift + $KEY_Use</b></color>] configure"));
			if (TollPolicy.Enabled && PortalStorage.Supports(__instance))
			{
				text2 += "\n<color=#ffd479>carry toll active</color> — stock the portal storage to bring ore through";
			}
			__result = __result + "\n" + Localization.instance.Localize(text + "\n" + text2);
		}
	}
	internal static class PortalStorage
	{
		internal const int Columns = 3;

		internal const int Rows = 2;

		private const string ChildName = "ISP_TollStorage";

		internal static bool Supports(TeleportWorld portal)
		{
			if ((Object)(object)portal != (Object)null)
			{
				return !portal.m_allowAllItems;
			}
			return false;
		}

		internal static void Attach(TeleportWorld portal)
		{
			//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_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			if (Supports(portal) && !((Object)(object)portal.m_nview == (Object)null) && portal.m_nview.GetZDO() != null && !((Object)(object)((Component)portal).transform.Find("ISP_TollStorage") != (Object)null))
			{
				GameObject val = new GameObject("ISP_TollStorage");
				val.SetActive(false);
				val.transform.SetParent(((Component)portal).transform, false);
				Container obj = val.AddComponent<Container>();
				obj.m_name = "Portal toll storage";
				obj.m_width = 3;
				obj.m_height = 2;
				obj.m_rootObjectOverride = portal.m_nview;
				obj.m_privacy = (PrivacySetting)2;
				obj.m_checkGuardStone = false;
				obj.m_autoDestroyEmpty = false;
				obj.m_bkg = ChestBackground();
				val.SetActive(true);
				Plugin.Debug($"Toll storage attached to portal {portal.m_nview.GetZDO().m_uid}.");
			}
		}

		internal static Container Get(TeleportWorld portal)
		{
			if ((Object)(object)portal == (Object)null)
			{
				return null;
			}
			Transform val = ((Component)portal).transform.Find("ISP_TollStorage");
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((Component)val).GetComponent<Container>();
		}

		internal static bool IsBuilder(TeleportWorld portal)
		{
			if ((Object)(object)portal == (Object)null)
			{
				return false;
			}
			Piece component = ((Component)portal).GetComponent<Piece>();
			if ((Object)(object)component == (Object)null)
			{
				return true;
			}
			long creator = component.GetCreator();
			if (creator == 0L)
			{
				return true;
			}
			PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null);
			if (val != null)
			{
				return val.GetPlayerID() == creator;
			}
			return false;
		}

		internal static void Open(TeleportWorld portal)
		{
			Container obj = Get(portal);
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)obj == (Object)null) && !((Object)(object)localPlayer == (Object)null))
			{
				if (!IsBuilder(portal))
				{
					((Character)localPlayer).Message((MessageType)2, "$msg_cantopen", 0, (Sprite)null);
					return;
				}
				long playerID = Game.instance.GetPlayerProfile().GetPlayerID();
				portal.m_nview.InvokeRPC("RequestOpen", new object[1] { playerID });
			}
		}

		internal static bool IsOpen(TeleportWorld portal)
		{
			Container val = Get(portal);
			if ((Object)(object)val != (Object)null && (Object)(object)InventoryGui.instance != (Object)null)
			{
				return (Object)(object)InventoryGui.instance.m_currentContainer == (Object)(object)val;
			}
			return false;
		}

		internal static int Count(TeleportWorld portal, string prefabName)
		{
			Container val = Get(portal);
			if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(prefabName))
			{
				return 0;
			}
			Inventory inventory = val.GetInventory();
			if (inventory == null)
			{
				return 0;
			}
			int num = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && !((Object)(object)allItem.m_dropPrefab == (Object)null) && ((Object)allItem.m_dropPrefab).name == prefabName)
				{
					num += allItem.m_stack;
				}
			}
			return num;
		}

		internal static bool Consume(TeleportWorld portal, string prefabName, int amount)
		{
			if (amount <= 0)
			{
				return true;
			}
			Container val = Get(portal);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (Count(portal, prefabName) < amount)
			{
				return false;
			}
			Inventory inventory = val.GetInventory();
			int num = amount;
			foreach (ItemData item in new List<ItemData>(inventory.GetAllItems()))
			{
				if (num <= 0)
				{
					break;
				}
				if (item != null && !((Object)(object)item.m_dropPrefab == (Object)null) && !(((Object)item.m_dropPrefab).name != prefabName))
				{
					int num2 = Mathf.Min(num, item.m_stack);
					inventory.RemoveItem(item, num2);
					num -= num2;
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogWarning((object)$"Toll consume fell short: wanted {amount}x {prefabName}, missed {num}.");
				return false;
			}
			val.Save();
			return true;
		}

		private static Sprite ChestBackground()
		{
			try
			{
				GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab("piece_chest_wood") : null);
				Container val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Container>() : null);
				return ((Object)(object)val2 != (Object)null) ? val2.m_bkg : null;
			}
			catch
			{
				return null;
			}
		}
	}
	[HarmonyPatch(typeof(TeleportWorld))]
	internal static class PortalVisuals
	{
		private sealed class VState
		{
			public bool Managed;

			public bool Active;

			public Color Hue;

			public float Alpha;

			public bool FxTinted;

			public bool FxCaptured;

			public ParticleSystem[] Particles;

			public MinMaxGradient[] OrigStartColors;

			public bool[] OrigColorOverLife;

			public MinMaxGradient[] OrigColorOverLifeColors;

			public List<MatCapture> Mats;

			public Light[] Lights;

			public Color[] OrigLightColors;
		}

		private sealed class MatCapture
		{
			public Material Mat;

			public int[] Props;

			public bool[] IsEmission;

			public Color[] Orig;
		}

		private const float EmissionBoost = 2f;

		private static readonly Color InterServerHue = new Color(0.49f, 0.827f, 0.988f);

		private static readonly Color NetworkHue = new Color(0.769f, 0.71f, 0.992f);

		private static readonly string[] TintPropNames = new string[4] { "_TintColor", "_Color", "_BaseColor", "_EmissionColor" };

		private static readonly Dictionary<TeleportWorld, VState> States = new Dictionary<TeleportWorld, VState>();

		private static readonly List<TeleportWorld> _dead = new List<TeleportWorld>();

		private static float _pruneAt = -999f;

		private static bool _returnCache;

		private static float _returnCacheAt = -999f;

		private static float _netCountAt = -999f;

		[HarmonyPatch("UpdatePortal")]
		[HarmonyPostfix]
		private static void UpdatePortal_Postfix(TeleportWorld __instance)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			PruneDead();
			ZNetView nview = __instance.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return;
			}
			bool flag = PortalData.IsInterServer(nview);
			bool flag2 = !flag && PortalData.IsNetwork(nview);
			if (!flag && !flag2)
			{
				if (States.TryGetValue(__instance, out var value))
				{
					RestoreFx(value);
					States.Remove(__instance);
				}
				return;
			}
			if (!States.TryGetValue(__instance, out var value2))
			{
				value2 = new VState();
				States[__instance] = value2;
			}
			value2.Managed = true;
			Color val = (flag ? InterServerHue : NetworkHue);
			if (value2.Hue != val)
			{
				value2.FxTinted = false;
			}
			value2.Hue = val;
			value2.Active = (flag ? IsInterServerActive(nview) : IsNetworkActive(nview));
			DriveSwirl(__instance, value2);
		}

		private static void DriveSwirl(TeleportWorld portal, VState st)
		{
			//IL_0079: 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)
			if ((Object)(object)portal.m_target_found != (Object)null && (Object)(object)portal.m_proximityRoot != (Object)null)
			{
				Player closestPlayer = Player.GetClosestPlayer(portal.m_proximityRoot.position, portal.m_activationRange);
				bool flag = (Object)(object)closestPlayer != (Object)null && (((Humanoid)closestPlayer).IsTeleportable() || portal.m_allowAllItems);
				portal.m_target_found.SetActive(flag && st.Active);
			}
			if (!st.FxTinted)
			{
				CaptureFx(portal, st);
				TintFx(st, st.Hue);
				st.FxTinted = true;
			}
		}

		private static void CaptureFx(TeleportWorld portal, VState st)
		{
			//IL_0061: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			if (st.FxCaptured)
			{
				return;
			}
			st.Particles = ((Component)portal).GetComponentsInChildren<ParticleSystem>(true);
			int num = st.Particles.Length;
			st.OrigStartColors = (MinMaxGradient[])(object)new MinMaxGradient[num];
			st.OrigColorOverLife = new bool[num];
			st.OrigColorOverLifeColors = (MinMaxGradient[])(object)new MinMaxGradient[num];
			for (int i = 0; i < num; i++)
			{
				ParticleSystem val = st.Particles[i];
				if (!((Object)(object)val == (Object)null))
				{
					MinMaxGradient[] origStartColors = st.OrigStartColors;
					int num2 = i;
					MainModule main = val.main;
					origStartColors[num2] = ((MainModule)(ref main)).startColor;
					ColorOverLifetimeModule colorOverLifetime = val.colorOverLifetime;
					st.OrigColorOverLife[i] = ((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled;
					st.OrigColorOverLifeColors[i] = ((ColorOverLifetimeModule)(ref colorOverLifetime)).color;
				}
			}
			st.Mats = new List<MatCapture>();
			ParticleSystem[] particles = st.Particles;
			foreach (ParticleSystem val2 in particles)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				ParticleSystemRenderer component = ((Component)val2).GetComponent<ParticleSystemRenderer>();
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				Material material = ((Renderer)component).material;
				if ((Object)(object)material == (Object)null)
				{
					continue;
				}
				List<int> list = new List<int>();
				List<bool> list2 = new List<bool>();
				List<Color> list3 = new List<Color>();
				string[] tintPropNames = TintPropNames;
				foreach (string text in tintPropNames)
				{
					int num3 = Shader.PropertyToID(text);
					if (material.HasProperty(num3))
					{
						bool flag = text == "_EmissionColor";
						Color color = material.GetColor(num3);
						if (!flag || !(((Color)(ref color)).maxColorComponent < 0.01f))
						{
							list.Add(num3);
							list2.Add(flag);
							list3.Add(color);
						}
					}
				}
				if (list.Count > 0)
				{
					st.Mats.Add(new MatCapture
					{
						Mat = material,
						Props = list.ToArray(),
						IsEmission = list2.ToArray(),
						Orig = list3.ToArray()
					});
				}
			}
			st.Lights = ((Component)portal).GetComponentsInChildren<Light>(true);
			st.OrigLightColors = (Color[])(object)new Color[st.Lights.Length];
			for (int l = 0; l < st.Lights.Length; l++)
			{
				if ((Object)(object)st.Lights[l] != (Object)null)
				{
					st.OrigLightColors[l] = st.Lights[l].color;
				}
			}
			st.FxCaptured = true;
		}

		private static void TintFx(VState st, Color hue)
		{
			//IL_0021: 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_0029: 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_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)
			//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_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			if (st.Particles != null)
			{
				ParticleSystem[] particles = st.Particles;
				foreach (ParticleSystem val in particles)
				{
					if (!((Object)(object)val == (Object)null))
					{
						MainModule main = val.main;
						((MainModule)(ref main)).startColor = new MinMaxGradient(hue);
						ColorOverLifetimeModule colorOverLifetime = val.colorOverLifetime;
						if (((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled)
						{
							((ColorOverLifetimeModule)(ref colorOverLifetime)).color = WhitenKeepAlpha(((ColorOverLifetimeModule)(ref colorOverLifetime)).color);
						}
					}
				}
			}
			if (st.Mats != null)
			{
				Color val2 = default(Color);
				foreach (MatCapture mat in st.Mats)
				{
					if ((Object)(object)mat.Mat == (Object)null)
					{
						continue;
					}
					for (int j = 0; j < mat.Props.Length; j++)
					{
						if (mat.IsEmission[j])
						{
							val2 = hue * Mathf.Max(((Color)(ref mat.Orig[j])).maxColorComponent, 1f);
						}
						else
						{
							((Color)(ref val2))..ctor(hue.r, hue.g, hue.b, mat.Orig[j].a);
						}
						mat.Mat.SetColor(mat.Props[j], val2);
					}
				}
			}
			if (st.Lights == null)
			{
				return;
			}
			Light[] lights = st.Lights;
			foreach (Light val3 in lights)
			{
				if ((Object)(object)val3 != (Object)null)
				{
					val3.color = hue;
				}
			}
		}

		private static void RestoreFx(VState st)
		{
			//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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: 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_007a: 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_00e5: Unknown result type (might be due to invalid IL or missing references)
			if (!st.FxCaptured)
			{
				return;
			}
			if (st.Particles != null && st.OrigStartColors != null)
			{
				int num = Mathf.Min(st.Particles.Length, st.OrigStartColors.Length);
				for (int i = 0; i < num; i++)
				{
					ParticleSystem val = st.Particles[i];
					if (!((Object)(object)val == (Object)null))
					{
						MainModule main = val.main;
						((MainModule)(ref main)).startColor = st.OrigStartColors[i];
						if (st.OrigColorOverLife != null && st.OrigColorOverLife[i])
						{
							ColorOverLifetimeModule colorOverLifetime = val.colorOverLifetime;
							((ColorOverLifetimeModule)(ref colorOverLifetime)).color = st.OrigColorOverLifeColors[i];
						}
					}
				}
			}
			if (st.Mats != null)
			{
				foreach (MatCapture mat in st.Mats)
				{
					if (!((Object)(object)mat.Mat == (Object)null))
					{
						for (int j = 0; j < mat.Props.Length; j++)
						{
							mat.Mat.SetColor(mat.Props[j], mat.Orig[j]);
						}
					}
				}
			}
			if (st.Lights == null || st.OrigLightColors == null)
			{
				return;
			}
			int num2 = Mathf.Min(st.Lights.Length, st.OrigLightColors.Length);
			for (int k = 0; k < num2; k++)
			{
				if ((Object)(object)st.Lights[k] != (Object)null)
				{
					st.Lights[k].color = st.OrigLightColors[k];
				}
			}
		}

		private static MinMaxGradient WhitenKeepAlpha(MinMaxGradient src)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_001e: Expected I4, but got Unknown
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			ParticleSystemGradientMode mode = ((MinMaxGradient)(ref src)).mode;
			switch ((int)mode)
			{
			case 0:
			{
				Color color = ((MinMaxGradient)(ref src)).color;
				color.r = (color.g = (color.b = 1f));
				return new MinMaxGradient(color);
			}
			case 2:
			{
				Color colorMin = ((MinMaxGradient)(ref src)).colorMin;
				colorMin.r = (colorMin.g = (colorMin.b = 1f));
				Color colorMax = ((MinMaxGradient)(ref src)).colorMax;
				colorMax.r = (colorMax.g = (colorMax.b = 1f));
				return new MinMaxGradient(colorMin, colorMax);
			}
			case 3:
				return new MinMaxGradient(Whiten(((MinMaxGradient)(ref src)).gradientMin), Whiten(((MinMaxGradient)(ref src)).gradientMax));
			default:
				return new MinMaxGradient(Whiten(((MinMaxGradient)(ref src)).gradient));
			}
		}

		private static Gradient Whiten(Gradient src)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_000e: 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_0024: 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_0033: 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)
			Gradient val = new Gradient();
			GradientColorKey[] array = (GradientColorKey[])(object)new GradientColorKey[2]
			{
				new GradientColorKey(Color.white, 0f),
				new GradientColorKey(Color.white, 1f)
			};
			val.SetKeys(array, src.alphaKeys);
			val.mode = src.mode;
			return val;
		}

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void Update_Postfix(TeleportWorld __instance)
		{
			//IL_0054: 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_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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)
			if (States.TryGetValue(__instance, out var value) && value.Managed)
			{
				MeshRenderer model = __instance.m_model;
				if (!((Object)(object)model == (Object)null))
				{
					value.Alpha = Mathf.MoveTowards(value.Alpha, value.Active ? 1f : 0f, Time.deltaTime);
					Color val = Color.Lerp(__instance.m_colorUnconnected, value.Hue * 2f, value.Alpha);
					((Renderer)model).material.SetColor("_EmissionColor", val);
				}
			}
		}

		private static bool IsInterServerActive(ZNetView nview)
		{
			if (PortalData.GetDestinations(nview).Count > 0)
			{
				return true;
			}
			if (InLocalWorld())
			{
				return ReturnAvailableCached();
			}
			return false;
		}

		private static bool IsNetworkActive(ZNetView nview)
		{
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			long num = zDO.GetLong(ZDOVars.s_creator, 0L);
			if (num == 0L)
			{
				return false;
			}
			RefreshNetworkCounts();
			return PortalNetwork.NetworkCount(num) >= 2;
		}

		private static void PruneDead()
		{
			if (Time.time - _pruneAt < 5f)
			{
				return;
			}
			_pruneAt = Time.time;
			_dead.Clear();
			foreach (TeleportWorld key in States.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					_dead.Add(key);
				}
			}
			foreach (TeleportWorld item in _dead)
			{
				States.Remove(item);
			}
		}

		private static bool InLocalWorld()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return !ZNet.instance.IsDedicated();
			}
			return false;
		}

		private static bool ReturnAvailableCached()
		{
			if (Time.time - _returnCacheAt > 3f)
			{
				_returnCache = ReturnRegistry.HasOrigin;
				_returnCacheAt = Time.time;
			}
			return _returnCache;
		}

		private static void RefreshNetworkCounts()
		{
			if (!(Time.time - _netCountAt < 1f))
			{
				_netCountAt = Time.time;
				PortalNetwork.RefreshCounts();
			}
		}
	}
}
namespace InterServerPortal.Policy
{
	internal static class ItemPolicy
	{
		internal static bool BlocksRestricted(TeleportWorld portal)
		{
			if ((Object)(object)portal != (Object)null)
			{
				return !portal.m_allowAllItems;
			}
			return false;
		}

		internal static bool CheckAndNotify(TeleportWorld portal, Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return true;
			}
			if (!BlocksRestricted(portal))
			{
				return true;
			}
			if (((Humanoid)player).IsTeleportable())
			{
				return true;
			}
			if (TollPolicy.Enabled)
			{
				return TollPolicy.CanPay(portal, player);
			}
			string text = Localization.instance.Localize("$msg_noteleport");
			List<string> list = OffendingItemNames(player);
			if (list.Count > 0)
			{
				text = text + ": " + string.Join(", ", list.ToArray());
			}
			((Character)player).Message((MessageType)2, text, 0, (Sprite)null);
			return false;
		}

		private static List<string> OffendingItemNames(Player player)
		{
			List<string> list = new List<string>();
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return list;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && !allItem.m_shared.m_teleportable)
				{
					string item = Localization.instance.Localize(allItem.m_shared.m_name);
					if (!list.Contains(item))
					{
						list.Add(item);
					}
				}
			}
			return list;
		}
	}
	internal enum TollChargeMode
	{
		PerStack,
		PerItem
	}
	internal class TollRule
	{
		public string Name;

		public bool Wildcard;

		public HashSet<string> Items = new HashSet<string>();

		public string FeePrefab;

		public int FeeAmount;

		public string BossKey;

		public bool Matches(string prefabName)
		{
			if (!Wildcard)
			{
				if (prefabName != null)
				{
					return Items.Contains(prefabName);
				}
				return false;
			}
			return true;
		}
	}
	internal static class TollPolicy
	{
		internal class Quote
		{
			public bool HasRestrictedCargo;

			public readonly List<string> MissingKeys = new List<string>();

			public readonly Dictionary<string, int> Fees = new Dictionary<string, int>();

			public readonly List<string> Shortfalls = new List<string>();

			public readonly List<string> Unpayable = new List<string>();
		}

		private static List<TollRule> _rules;

		internal static bool Enabled
		{
			get
			{
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					return Plugin.Instance.TollEnabled.Value;
				}
				return false;
			}
		}

		internal static List<TollRule> Rules
		{
			get
			{
				if (_rules == null)
				{
					_rules = ParseRules();
				}
				return _rules;
			}
		}

		internal static void InvalidateRules()
		{
			_rules = null;
		}

		internal static Quote Evaluate(TeleportWorld portal, Player player)
		{
			Quote quote = new Quote();
			if ((Object)(object)portal == (Object)null || (Object)(object)player == (Object)null)
			{
				return quote;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return quote;
			}
			bool value = Plugin.Instance.TollRequireBossKey.Value;
			bool flag = Plugin.Instance.TollChargeMode.Value == TollChargeMode.PerItem;
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem == null || allItem.m_shared == null || allItem.m_shared.m_teleportable)
				{
					continue;
				}
				quote.HasRestrictedCargo = true;
				TollRule tollRule = FindRule(((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : null);
				if (tollRule == null)
				{
					string item = Localize(allItem.m_shared.m_name);
					if (!quote.Unpayable.Contains(item))
					{
						quote.Unpayable.Add(item);
					}
					continue;
				}
				if (value && !string.IsNullOrEmpty(tollRule.BossKey) && !HasGlobalKey(tollRule.BossKey))
				{
					string item2 = BossName(tollRule.BossKey);
					if (hashSet.Add(tollRule.BossKey))
					{
						quote.MissingKeys.Add(item2);
					}
				}
				if (!string.IsNullOrEmpty(tollRule.FeePrefab) && tollRule.FeeAmount > 0)
				{
					int num = ((!flag) ? 1 : Mathf.Max(1, allItem.m_stack));
					int num2 = tollRule.FeeAmount * num;
					quote.Fees.TryGetValue(tollRule.FeePrefab, out var value2);
					quote.Fees[tollRule.FeePrefab] = value2 + num2;
				}
			}
			foreach (KeyValuePair<string, int> fee in quote.Fees)
			{
				int num3 = PortalStorage.Count(portal, fee.Key);
				if (num3 < fee.Value)
				{
					quote.Shortfalls.Add($"{fee.Value}x {ItemName(fee.Key)} (have {num3})");
				}
			}
			return quote;
		}

		internal static bool CanPay(TeleportWorld portal, Player player)
		{
			Quote quote = Evaluate(portal, player);
			if (quote.HasRestrictedCargo)
			{
				return Explain(quote, player);
			}
			return true;
		}

		internal static bool Charge(TeleportWorld portal, Player player)
		{
			Quote quote = Evaluate(portal, player);
			if (!quote.HasRestrictedCargo)
			{
				return true;
			}
			if (!Explain(quote, player))
			{
				return false;
			}
			foreach (KeyValuePair<string, int> fee in quote.Fees)
			{
				if (PortalStorage.Count(portal, fee.Key) < fee.Value)
				{
					Notify(player, "Portal toll — storage changed, try again.");
					return false;
				}
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, int> fee2 in quote.Fees)
			{
				if (!PortalStorage.Consume(portal, fee2.Key, fee2.Value))
				{
					Plugin.Log.LogError((object)$"Toll charge failed mid-payment for {fee2.Value}x {fee2.Key} — travel denied.");
					Notify(player, "Portal toll — could not charge storage.");
					return false;
				}
				list.Add($"{fee2.Value}x {ItemName(fee2.Key)}");
			}
			if (list.Count > 0)
			{
				Notify(player, "Portal toll paid: " + string.Join(", ", list.ToArray()));
			}
			Plugin.Debug("Toll paid on portal — " + string.Join(", ", list.ToArray()) + ".");
			return true;
		}

		private static bool Explain(Quote quote, Player player)
		{
			if (quote.Unpayable.Count > 0)
			{
				Notify(player, "$msg_noteleport: " + string.Join(", ", quote.Unpayable.ToArray()));
				return false;
			}
			if (quote.MissingKeys.Count > 0)
			{
				Notify(player, "Portal toll — first defeat: " + string.Join(", ", quote.MissingKeys.ToArray()));
				return false;
			}
			if (quote.Shortfalls.Count > 0)
			{
				Notify(player, "Portal toll — storage needs " + string.Join(", ", quote.Shortfalls.ToArray()));
				return false;
			}
			return true;
		}

		internal static string RequirementsText()
		{
			if (!Enabled)
			{
				return "Carry toll is <color=grey>disabled</color> in the config.\nWood portals refuse ore exactly like vanilla.";
			}
			bool value = Plugin.Instance.TollRequireBossKey.Value;
			string value2 = ((Plugin.Instance.TollChargeMode.Value == TollChargeMode.PerItem) ? "per item" : "per stack");
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Deposit the fee here, then walk through carrying the cargo.\n");
			stringBuilder.Append("Charged <color=#7dd3fc>").Append(value2).Append("</color>");
			stringBuilder.Append(value ? ", boss key required.\n\n" : ".\n\n");
			foreach (TollRule rule in Rules)
			{
				if (!rule.Wildcard && rule.Items.Count != 0)
				{
					string value3 = FriendlyList(rule.Items);
					string value4 = ((string.IsNullOrEmpty(rule.FeePrefab) || rule.FeeAmount <= 0) ? "free" : $"{rule.FeeAmount}x {ItemName(rule.FeePrefab)}");
					stringBuilder.Append("• <color=#ffd479>").Append(value3).Append("</color> — ")
						.Append(value4);
					if (value && !string.IsNullOrEmpty(rule.BossKey))
					{
						bool flag = HasGlobalKey(rule.BossKey);
						string value5 = (flag ? "#9ae6b4" : "#ffb0b0");
						stringBuilder.Append("  <color=").Append(value5).Append('>')
							.Append(flag ? "✓ " : "✗ ")
							.Append(BossName(rule.BossKey))
							.Append("</color>");
					}
					stringBuilder.Append('\n');
				}
			}
			return stringBuilder.ToString();
		}

		private static TollRule FindRule(string prefabName)
		{
			TollRule tollRule = null;
			foreach (TollRule rule in Rules)
			{
				if (rule.Wildcard)
				{
					if (tollRule == null)
					{
						tollRule = rule;
					}
				}
				else if (rule.Matches(prefabName))
				{
					return rule;
				}
			}
			return tollRule;
		}

		private static List<TollRule> ParseRules()
		{
			List<TollRule> list = new List<TollRule>();
			if ((Object)(object)Plugin.Instance == (Object)null)
			{
				return list;
			}
			ConfigEntry<string>[] tollRules = Plugin.Instance.TollRules;
			foreach (ConfigEntry<string> val in tollRules)
			{
				TollRule tollRule = ParseRule(((ConfigEntryBase)val).Definition.Key, val.Value);
				if (tollRule != null)
				{
					list.Add(tollRule);
				}
			}
			TollRule tollRule2 = ParseRule("99_Default", Plugin.Instance.TollDefaultRule.Value);
			if (tollRule2 != null)
			{
				list.Add(tollRule2);
			}
			return list;
		}

		private static TollRule ParseRule(string name, string raw)
		{
			if (string.IsNullOrEmpty(raw))
			{
				return null;
			}
			TollRule tollRule = new TollRule
			{
				Name = name
			};
			string[] array = raw.Split(new char[1] { '|' });
			foreach (string text in array)
			{
				int num = text.IndexOf('=');
				if (num < 0)
				{
					continue;
				}
				string text2 = text.Substring(0, num).Trim().ToLowerInvariant();
				string text3 = text.Substring(num + 1).Trim();
				switch (text2)
				{
				case "items":
				{
					if (text3 == "*")
					{
						tollRule.Wildcard = true;
						break;
					}
					string[] array2 = text3.Split(new char[1] { ',' });
					for (int j = 0; j < array2.Length; j++)
					{
						string text4 = array2[j].Trim();
						if (text4.Length > 0)
						{
							tollRule.Items.Add(text4);
						}
					}
					break;
				}
				case "fee":
				{
					int num2 = text3.LastIndexOf(':');
					if (num2 > 0)
					{
						tollRule.FeePrefab = text3.Substring(0, num2).Trim();
						tollRule.FeeAmount = (int.TryParse(text3.Substring(num2 + 1).Trim(), out var result) ? result : 0);
					}
					break;
				}
				case "key":
					tollRule.BossKey = text3;
					break;
				}
			}
			if (!tollRule.Wildcard && tollRule.Items.Count == 0)
			{
				return null;
			}
			return tollRule;
		}

		private static bool HasGlobalKey(string key)
		{
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				return ZoneSystem.instance.GetGlobalKey(key);
			}
			return false;
		}

		private static string BossName(string key)
		{
			return key.ToLowerInvariant() switch
			{
				"defeated_eikthyr" => "Eikthyr", 
				"defeated_gdking" => "The Elder", 
				"defeated_bonemass" => "Bonemass", 
				"defeated_dragon" => "Moder", 
				"defeated_goblinking" => "Yagluth", 
				"defeated_queen" => "The Queen", 
				"defeated_fader" => "Fader", 
				_ => key, 
			};
		}

		internal static string ItemName(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return "";
			}
			try
			{
				GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefabName) : null);
				ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val2 != (Object)null && val2.m_itemData != null && val2.m_itemData.m_shared != null)
				{
					return Localize(val2.m_itemData.m_shared.m_name);
				}
			}
			catch
			{
			}
			return prefabName;
		}

		private static string FriendlyList(HashSet<string> prefabs)
		{
			List<string> list = new List<string>();
			foreach (string prefab in prefabs)
			{
				string item = ItemName(prefab);
				if (!list.Contains(item))
				{
					list.Add(item);
				}
			}
			return string.Join(", ", list.ToArray());
		}

		private static string Localize(string token)
		{
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}

		private static void Notify(Player player, string message)
		{
			if ((Object)(object)player != (Object)null)
			{
				((Character)player).Message((MessageType)2, Localize(message), 0, (Sprite)null);
			}
		}
	}
}
namespace InterServerPortal.Patches
{
	[HarmonyPatch(typeof(FejdStartup), "Start")]
	internal static class FejdStartup_Start_Patch
	{
		private static void Postfix(FejdStartup __instance)
		{
			if (WorldSwitcher.Pending != null)
			{
				Plugin.Log.LogInfo((object)"FejdStartup reached with a pending switch — resuming.");
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(WorldSwitcher.ResumePending(__instance));
				}
			}
		}
	}
}
namespace InterServerPortal.Net
{
	internal static class DiscordNotifier
	{
		private static bool _tlsConfigured;

		internal static void NotifyTravelToLocal(string playerName, string worldName, string fromServer)
		{
			string text = Plugin.Instance?.DiscordWebhookUrl?.Value;
			if (!string.IsNullOrEmpty(text))
			{
				string text2 = (string.IsNullOrEmpty(playerName) ? "A player" : playerName);
				string text3 = (string.IsNullOrEmpty(worldName) ? "their local world" : ("their local world **" + worldName + "**"));
				string text4 = (string.IsNullOrEmpty(fromServer) ? "" : (" (from **" + fromServer + "**)"));
				string content = "\ud83c\udf00 **" + text2 + "** stepped through a portal to " + text3 + text4 + ".";
				Post(text, content);
			}
		}

		private static void Post(string url, string content)
		{
			string json = "{\"content\":\"" + Escape(content) + "\"}";
			ThreadPool.QueueUserWorkItem(delegate
			{
				Send(url, json);
			});
		}

		private static void Send(string url, string json)
		{
			HttpWebResponse httpWebResponse2 = default(HttpWebResponse);
			try
			{
				EnsureTls();
				HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
				httpWebRequest.Method = "POST";
				httpWebRequest.ContentType = "application/json";
				httpWebRequest.UserAgent = "InterServerPortal";
				httpWebRequest.Timeout = 10000;
				byte[] bytes = Encoding.UTF8.GetBytes(json);
				httpWebRequest.ContentLength = bytes.Length;
				using (Stream stream = httpWebRequest.GetRequestStream())
				{
					stream.Write(bytes, 0, bytes.Length);
				}
				using HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
				Plugin.Debug($"Discord travel notification sent ({(int)httpWebResponse.StatusCode}).");
			}
			catch (WebException ex) when (((Func<bool>)delegate
			{
				// Could not convert BlockContainer to single expression
				httpWebResponse2 = ex.Response as HttpWebResponse;
				return httpWebResponse2 != null;
			}).Invoke())
			{
				string arg = "";
				try
				{
					using StreamReader streamReader = new StreamReader(httpWebResponse2.GetResponseStream());
					arg = streamReader.ReadToEnd();
				}
				catch
				{
				}
				Plugin.Log.LogWarning((object)$"Discord notify failed: HTTP {(int)httpWebResponse2.StatusCode} {arg}");
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Discord notify failed: " + ex2.Message));
			}
		}

		private static void EnsureTls()
		{
			if (!_tlsConfigured)
			{
				ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
				ServicePointManager.ServerCertificateValidationCallback = (RemoteCertificateValidationCallback)Delegate.Combine(ServicePointManager.ServerCertificateValidationCallback, (RemoteCertificateValidationCallback)((object s, X509Certificate cert, X509Chain chain, SslPolicyErrors errors) => true));
				_tlsConfigured = true;
			}
		}

		private static string Escape(string s)
		{
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			foreach (char c in s)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					break;
				case '"':
					stringBuilder.Append("\\\"");
					break;
				case '\n':
					stringBuilder.Append("\\n");
					break;
				case '\r':
					stringBuilder.Append("\\r");
					break;
				case '\t':
					stringBuilder.Append("\\t");
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	internal struct NetPortal
	{
		public ZDOID Id;

		public Vector3 Pos;

		public Quaternion Rot;

		public string Tag;
	}
	internal static class PortalNetwork
	{
		private const string RpcRequest = "ISP_ReqNet";

		private const string RpcResponse = "ISP_RespNet";

		private const string RpcReqCounts = "ISP_ReqCounts";

		private const string RpcRespCounts = "ISP_RespCounts";

		private const float RequestTimeout = 5f;

		private static readonly int LinkHash = StringExtensionMethods.GetStableHashCode("ISP.link");

		private static readonly int EnabledHash = StringExtensionMethods.GetStableHashCode("ISP.enabled");

		private static ZRoutedRpc _registeredOn;

		private static Action<List<NetPortal>> _pending;

		private static float _pendingSince;

		private static readonly Dictionary<long, int> _counts = new Dictionary<long, int>();

		private static float _clientReqAt = -999f;

		private const float CountsClientInterval = 3f;

		internal static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredOn)
			{
				instance.Register<ZPackage>("ISP_ReqNet", (Action<long, ZPackage>)OnServerRequest);
				instance.Register<ZPackage>("ISP_RespNet", (Action<long, ZPackage>)OnClientResponse);
				instance.Register<ZPackage>("ISP_ReqCounts", (Action<long, ZPackage>)OnServerCountsRequest);
				instance.Register<ZPackage>("ISP_RespCounts", (Action<long, ZPackage>)OnClientCountsResponse);
				_registeredOn = instance;
				_pending = null;
				_counts.Clear();
				Plugin.Debug("Portal-network RPCs registered.");
			}
		}

		internal static void Tick()
		{
			if (_pending != null && Time.time - _pendingSince > 5f)
			{
				_pending = null;
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					((Character)localPlayer).Message((MessageType)2, "Portal network unavailable (server did not respond).", 0, (Sprite)null);
				}
			}
		}

		internal static void Request(TeleportWorld source, Action<List<NetPortal>> onResult)
		{
			//IL_0039: 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_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_0089: 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)
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)source == (Object)null) && !((Object)(object)source.m_nview == (Object)null))
			{
				long playerID = localPlayer.GetPlayerID();
				ZDOID uid = source.m_nview.GetZDO().m_uid;
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					onResult(Enumerate(playerID, uid));
					return;
				}
				EnsureRegistered();
				_pending = onResult;
				_pendingSince = Time.time;
				ZPackage val = new ZPackage();
				val.Write(playerID);
				val.Write(uid);
				ZRoutedRpc.instance.InvokeRoutedRPC("ISP_ReqNet", new object[1] { val });
			}
		}

		private static void OnServerRequest(long sender, ZPackage pkg)
		{
			//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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_003b: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			long playerId = pkg.ReadLong();
			ZDOID exclude = pkg.ReadZDOID();
			List<NetPortal> list = Enumerate(playerId, exclude);
			ZPackage val = new ZPackage();
			val.Write(list.Count);
			foreach (NetPortal item in list)
			{
				val.Write(item.Id);
				val.Write(item.Pos);
				val.Write(item.Rot);
				val.Write(item.Tag ?? "");
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ISP_RespNet", new object[1] { val });
		}

		private static void OnClientResponse(long sender, ZPackage pkg)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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)
			int num = pkg.ReadInt();
			List<NetPortal> list = new List<NetPortal>(num);
			for (int i = 0; i < num; i++)
			{
				list.Add(new NetPortal
				{
					Id = pkg.ReadZDOID(),
					Pos = pkg.ReadVector3(),
					Rot = pkg.ReadQuaternion(),
					Tag = pkg.ReadString()
				});
			}
			Action<List<NetPortal>> pending = _pending;
			_pending = null;
			pending?.Invoke(list);
		}

		internal static List<NetPortal> Enumerate(long playerId, ZDOID exclude)
		{
			//IL_0038: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			List<NetPortal> list = new List<NetPortal>();
			if (ZDOMan.instance == null)
			{
				return list;
			}
			foreach (ZDO portal in ZDOMan.instance.GetPortals())
			{
				if (portal != null && portal.IsValid() && !(portal.m_uid == exclude) && IsNetworkPortal(portal) && portal.GetLong(ZDOVars.s_creator, 0L) == playerId)
				{
					list.Add(new NetPortal
					{
						Id = portal.m_uid,
						Pos = portal.GetPosition(),
						Rot = portal.GetRotation(),
						Tag = portal.GetString(ZDOVars.s_tag, "")
					});
				}
			}
			return list;
		}

		private static bool IsNetworkPortal(ZDO zdo)
		{
			if (zdo.GetInt(EnabledHash, 0) != 0)
			{
				return false;
			}
			return zdo.GetInt(LinkHash, 0) == 1;
		}

		internal static int NetworkCount(long creator)
		{
			_counts.TryGetValue(creator, out var value);
			return value;
		}

		internal static void RefreshCounts()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			if (ZDOMan.instance == null)
			{
				return;
			}
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				BuildCounts(_counts);
				return;
			}
			EnsureRegistered();
			if (ZRoutedRpc.instance != null && !(Time.time - _clientReqAt < 3f))
			{
				_clientReqAt = Time.time;
				ZRoutedRpc.instance.InvokeRoutedRPC("ISP_ReqCounts", new object[1] { (object)new ZPackage() });
			}
		}

		private static void BuildCounts(Dictionary<long, int> dict)
		{
			dict.Clear();
			foreach (ZDO portal in ZDOMan.instance.GetPortals())
			{
				if (portal != null && portal.IsValid() && IsNetworkPortal(portal))
				{
					long num = portal.GetLong(ZDOVars.s_creator, 0L);
					if (num != 0L)
					{
						dict.TryGetValue(num, out var value);
						dict[num] = value + 1;
					}
				}
			}
		}

		private static void OnServerCountsRequest(long sender, ZPackage pkg)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			BuildCounts(dictionary);
			ZPackage val = new ZPackage();
			val.Write(dictionary.Count);
			foreach (KeyValuePair<long, int> item in dictionary)
			{
				val.Write(item.Key);
				val.Write(item.Value);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ISP_RespCounts", new object[1] { val });
		}

		private static void OnClientCountsResponse(long sender, ZPackage pkg)
		{
			int num = pkg.ReadInt();
			_counts.Clear();
			for (int i = 0; i < num; i++)
			{
				long key = pkg.ReadLong();
				int value = pkg.ReadInt();
				_counts[key] = value;
			}
		}
	}
}
namespace InterServerPortal.Hub
{
	internal static class CodePrompt
	{
		internal static void Show(TeleportWorld portal, Action onSuccess)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			float num = 234f;
			HubWindow hubWindow = HubWindow.Open("Locked portal", 400f, num);
			if ((Object)(object)hubWindow == (Object)null)
			{
				onSuccess?.Invoke();
				return;
			}
			float cursor = num / 2f - 66f;
			hubWindow.AddLabel("Enter the portal's code:", 0f, Place(28f, 18f), 340f, 28f, 16, Color.white);
			InputField input = hubWindow.AddInput("Code", 0f, Place(38f, 18f), 300f, 38f, (ContentType)7);
			float y = Place(40f, 26f);
			hubWindow.AddButton("Enter", -90f, y, 150f, 40f, delegate
			{
				string entered = (((Object)(object)input != (Object)null) ? input.text : "");
				if (PortalData.CheckCode(portal.m_nview, entered))
				{
					HubWindow.Close();
					onSuccess?.Invoke();
				}
				else
				{
					LockGate.RegisterFail(portal);
					HubWindow.Close();
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer != (Object)null)
					{
						((Character)localPlayer).Message((MessageType)2, "Incorrect code.", 0, (Sprite)null);
					}
				}
			});
			hubWindow.AddButton("Cancel", 90f, y, 150f, 40f, HubWindow.Close);
			float Place(float h, float gapAfter)
			{
				float result = cursor - h / 2f;
				cursor -= h + gapAfter;
				return result;
			}
		}
	}
	internal static class DestinationMenu
	{
		internal static void Show(List<HubEntry> entries)
		{
			float num = 130f + (float)entries.Count * 52f;
			HubWindow hubWindow = HubWindow.Open("Select destination", 400f, num);
			if ((Object)(object)hubWindow == (Object)null)
			{
				return;
			}
			float num2 = num / 2f - 78f;
			for (int i = 0; i < entries.Count; i++)
			{
				HubEntry entry = entries[i];
				float y = num2 - (float)i * 52f;
				string text = (entry.IsActionable ? entry.Label : (entry.Label + "  —  " + entry.UnavailableReason));
				hubWindow.AddButton(text, 0f, y, 350f, 44f, delegate
				{
					HubController.Go(entry);
				}, entry.IsActionable);
			}
			hubWindow.AddButton("Cancel", 0f, (0f - num) / 2f + 34f, 150f, 38f, HubWindow.Close);
		}
	}
	internal class HubEntry
	{
		public string Label;

		public Destination Destination;

		public bool IsServerReturn;

		public DestinationValidator.Availability Availability;

		public string UnavailableReason;

		public bool IsActionable
		{
			get
			{
				if (!IsServerReturn)
				{
					return Availability == DestinationValidator.Availability.Available;
				}
				return true;
			}
		}
	}
	internal static class HubController
	{
		private static TeleportWorld _origin;

		internal static void BeginTravel(TeleportWorld portal)
		{
			if ((Object)(object)portal == (Object)null || HubWindow.IsOpen)
			{
				return;
			}
			_origin = portal;
			if (WorldSwitcher.InProgress || !WorldSwitcher.PortalArmed)
			{
				return;
			}
			if (PortalData.IsLocked(portal.m_nview) && !LockGate.IsUnlocked(portal))
			{
				if (LockGate.InCooldown(portal, out var remaining))
				{
					Notify($"Portal locked — wait {Mathf.CeilToInt(remaining)}s before trying again.");
					return;
				}
				CodePrompt.Show(portal, delegate
				{
					LockGate.MarkUnlocked(portal);
					ProceedTravel(portal);
				});
			}
			else
			{
				ProceedTravel(portal);
			}
		}

		private static void ProceedTravel(TeleportWorld portal)
		{
			if (!ItemPolicy.CheckAndNotify(portal, Player.m_localPlayer))
			{
				return;
			}
			List<Destination> destinations = PortalData.GetDestinations(portal.m_nview);
			bool flag = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && !ZNet.instance.IsDedicated();
			string b = ((ZNet.World != null) ? ZNet.World.m_name : "");
			List<HubEntry> list = new List<HubEntry>();
			if (flag && ReturnRegistry.HasOrigin)
			{
				list.Add(new HubEntry
				{
					IsServerReturn = true,
					Label = "Return to origin server"
				});
			}
			foreach (Destination item in destinations)
			{
				if (!flag || !string.Equals(item.WorldName, b, StringComparison.OrdinalIgnoreCase))
				{
					DestinationValidator.Result result = DestinationValidator.Validate(item.WorldName);
					list.Add(new HubEntry
					{
						Destination = item,
						Label = item.Label,
						Availability = result.Status,
						UnavailableReason = (result.IsAvailable ? null : result.Reason)
					});
				}
			}
			if (list.Count == 0)
			{
				Notify("This portal has no destinations. Use L.Shift+Use to configure one.");
			}
			else if (list.Count == 1 && list[0].IsActionable)
			{
				Go(list[0]);
			}
			else
			{
				DestinationMenu.Show(list);
			}
		}

		internal static void Go(HubEntry entry)
		{
			HubWindow.Close();
			if (entry != null && entry.IsActionable && (!TollPolicy.Enabled || TollPolicy.Charge(_origin, Player.m_localPlayer)))
			{
				if (entry.IsServerReturn)
				{
					WorldSwitcher.RequestReturnToServer();
				}
				else
				{
					WorldSwitcher.RequestSwitchToLocal(entry.Destination.WorldName);
				}
			}
		}

		private static void Notify(string message)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null);
			}
		}
	}
	internal class HubWindow : MonoBehaviour
	{
		private static HubWindow _current;

		private float _width;

		private float _height;

		private bool _blockedInput;

		private bool _closeWithInventory;

		private bool _inventorySeen;

		internal static bool IsOpen => (Object)(object)_current != (Object)null;

		internal GameObject Panel { get; private set; }

		internal static HubWindow Open(string title, float width, float height, Vector2? anchor = null, Vector2? position = null, bool blockInput = true, bool closeWithInventory = false)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			if (GUIManager.IsHeadless() || GUIManager.Instance == null || (Object)(object)GUIManager.CustomGUIFront == (Object)null)
			{
				Plugin.Log.LogWarning((object)"HubWindow.Open: GUI not available (headless or too early).");
				return null;
			}
			Close();
			HubWindow hubWindow = new GameObject("ISP_HubWindow").AddComponent<HubWindow>();
			hubWindow._width = width;
			hubWindow._height = height;
			hubWindow._closeWithInventory = closeWithInventory;
			hubWindow.Build(title, (Vector2)(((??)anchor) ?? new Vector2(0.5f, 0.5f)), (Vector2)(((??)position) ?? Vector2.zero));
			_current = hubWindow;
			if (blockInput)
			{
				hubWindow._blockedInput = true;
				GUIManager.BlockInput(true);
			}
			return hubWindow;
		}

		internal static void Close()
		{
			if (!((Object)(object)_current == (Object)null))
			{
				if (_current._blockedInput)
				{
					GUIManager.BlockInput(false);
				}
				if ((Object)(object)_current.Panel != (Object)null)
				{
					Object.Destroy((Object)(object)_current.Panel);
				}
				Object.Destroy((Object)(object)((Component)_current).gameObject);
				_current = null;
			}
		}

		private void Build(string title, Vector2 anchor, Vector2 position)
		{
			//IL_0010: 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_0012: 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)
			Panel = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, anchor, anchor, position, _width, _height, true);
			Panel.SetActive(true);
			AddLabel(title, 0f, _height / 2f - 34f, _width - 40f, 40f, 24, GUIManager.Instance.ValheimOrange);
		}

		private void Update()
		{
			if (Input.GetKeyDown((KeyCode)27))
			{
				Close();
			}
			else if (_closeWithInventory)
			{
				if (InventoryGui.IsVisible())
				{
					_inventorySeen = true;
				}
				else if (_inventorySeen)
				{
					Close();
				}
			}
		}

		internal Text AddLabel(string text, float x, float y, float width, float height, int fontSize, Color color)
		{
			//IL_001b: 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_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			return GUIManager.Instance.CreateText(text, Panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), GUIManager.Instance.AveriaSerifBold, fontSize, color, true, Color.black, width, height, false).GetComponent<Text>();
		}

		internal Button AddButton(string text, float x, float y, float width, float height, Action onClick, bool interactable = true)
		{
			//IL_0029: 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)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			GameObject obj = GUIManager.Instance.CreateButton(text, Panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), width, height);
			obj.SetActive(true);
			Button component = obj.GetComponent<Button>();
			((Selectable)component).interactable = interactable;
			if (onClick != null)
			{
				((UnityEvent)component.onClick).AddListener((UnityAction)delegate
				{
					onClick();
				});
			}
			return component;
		}

		internal InputField AddInput(string placeholder, float x, float y, float width, float height, ContentType contentType = (ContentType)0)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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)
			GameObject obj = GUIManager.Instance.CreateInputField(Panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), contentType, placeholder, 16, width, height);
			obj.SetActive(true);
			return obj.GetComponent<InputField>();
		}
	}
	internal static class NetworkController
	{
		private static float _suppressUntil;

		internal static bool ArrivalSuppressed => Time.time < _suppressUntil;

		internal static void Begin(TeleportWorld portal)
		{
			if ((Object)(object)portal == (Object)null || HubWindow.IsOpen || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			if (PortalData.IsLocked(portal.m_nview) && !LockGate.IsUnlocked(portal))
			{
				if (LockGate.InCooldown(portal, out var remaining))
				{
					Notify($"Portal locked — wait {Mathf.CeilToInt(remaining)}s before trying again.");
					return;
				}
				CodePrompt.Show(portal, delegate
				{
					LockGate.MarkUnlocked(portal);
					Proceed(portal);
				});
			}
			else
			{
				Proceed(portal);
			}
		}

		private static void Proceed(TeleportWorld portal)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && ItemPolicy.CheckAndNotify(portal, localPlayer))
			{
				PortalNetwork.Request(portal, delegate(List<NetPortal> list)
				{
					PortalNetworkMenu.Show(portal, list);
				});
			}
		}

		internal static void Travel(TeleportWorld source, NetPortal target)
		{
			//IL_0030: 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)
			//IL_003f: 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_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			HubWindow.Close();
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)source == (Object)null) && (!TollPolicy.Enabled || TollPolicy.Charge(source, localPlayer)))
			{
				Vector3 val = target.Rot * Vector3.forward;
				Vector3 val2 = target.Pos + val * source.m_exitDistance + Vector3.up;
				((Character)localPlayer).TeleportTo(val2, target.Rot, true);
				_suppressUntil = Time.time + 2f;
				Plugin.Debug("Network teleport → " + (string.IsNullOrEmpty(target.Tag) ? "(unnamed)" : target.Tag) + ".");
			}
		}

		private static void Notify(string message)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null);
			}
		}
	}
	internal static class PortalConfigPanel
	{
		private enum CodeMode
		{
			Unchanged,
			Set,
			Clear
		}

		private static TeleportWorld _portal;

		private static ZNetView _nview;

		private static bool _enabled;

		private static List<Destination> _dests;

		private static bool _lockedInitially;

		private static CodeMode _codeMode;

		private static string _codeValue;

		private static int _linkMode;

		private const float ColWidth = 340f;

		private const float ColGap = 24f;

		private const float Width = 744f;

		private const float LeftX = -182f;

		private const float RightX = 182f;

		private const float ScreenMargin = 24f;

		private const float TopMargin = 74f;

		private const float FlagH = 40f;

		private const float HeaderH = 30f;

		private const float RowH = 34f;

		private const float RowGap = 10f;

		private const float AddRowH = 36f;

		private const float AddBtnH = 38f;

		private const float SaveH = 42f;

		private const float SectionGap = 18f;

		private const float BottomMargin = 26f;

		private const float ReqLineH = 19f;

		internal static void Show(TeleportWorld portal)
		{
			if (!((Object)(object)portal == (Object)null) && !((Object)(object)portal.m_nview == (Object)null))
			{
				_portal = portal;
				_nview = portal.m_nview;
				_enabled = PortalData.IsInterServer(_nview);
				_dests = PortalData.GetDestinations(_nview);
				_lockedInitially = PortalData.IsLocked(_nview);
				_codeMode = CodeMode.Unchanged;
				_codeValue = "";
				_linkMode = PortalData.GetLinkMode(_nview);
				Rebuild();
			}
		}

		private static void Rebuild()
		{
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			int num = ((_dests.Count <= 0) ? 1 : _dests.Count);
			float num2 = 230f + (float)num * 44f + 18f + 36f + 18f + 38f + 18f + 30f + 10f + 36f + 10f + 38f + 18f + 42f + 26f;
			string text = TollRequirementsText();
			float num3 = RequirementsHeight(text);
			float num4 = 114f + num3 + 18f + 38f + 26f;
			float height = Mathf.Max(num2, num4);
			bool flag = ShouldOpenStorage();
			HubWindow hubWindow = HubWindow.Open("Configure portal", 744f, height, (Vector2?)new Vector2(1f, 0.5f), (Vector2?)new Vector2(-396f, 0f), !flag, flag);
			if (!((Object)(object)hubWindow == (Object)null))
			{
				BuildLeftColumn(hubWindow, height);
				BuildRightColumn(hubWindow, height, text, num3);
				if (flag && !PortalStorage.IsOpen(_portal))
				{
					PortalStorage.Open(_portal);
				}
			}
		}

		private static void BuildLeftColumn(HubWindow win, float height)
		{
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			float cursor = height / 2f - 74f;
			win.AddButton(_enabled ? "Inter-server portal: ON" : "Inter-server portal: OFF", -182f, Place(40f, 18f), 340f, 40f, delegate
			{
				_enabled = !_enabled;
				Rebuild();
			});
			string text = ((_linkMode == 1) ? "Same-world link: NETWORK" : "Same-world link: TAG pair");
			win.AddButton(text, -182f, Place(40f, 18f), 340f, 40f, delegate
			{
				_linkMode = ((_linkMode != 1) ? 1 : 0);
				Rebuild();
			});
			win.AddLabel("Destinations", -182f, Place(30f, 10f), 340f, 30f, 18, GUIManager.Instance.ValheimOrange);
			if (_dests.Count == 0)
			{
				win.AddLabel("(none yet — add one below)", -182f, Place(34f, 10f), 320f, 34f, 14, Color.gray);
			}
			else
			{
				foreach (Destination dest in _dests)
				{
					Destination captured = dest;
					float y = Place(34f, 10f);
					win.AddLabel(dest.Label + "  (" + dest.WorldName + ")", -237f, y, 210f, 34f, 15, Color.white);
					win.AddButton("Remove", -62f, y, 100f, 30f, delegate
					{
						_dests.Remove(captured);
						Rebuild();
					});
				}
			}
			cursor -= 8f;
			float y2 = Place(36f, 18f);
			InputField labelInput = win.AddInput("Label (optional)", -270f, y2, 155f, 36f, (ContentType)0);
			List<string> worldNames = LocalWorldNames();
			Dropdown dropdown = CreateWorldDropdown(win, -94f, y2, worldNames);
			win.AddButton("Add destination", -182f, Place(38f, 18f), 240f, 38f, delegate
			{
				string text2 = SelectedWorld(dropdown, worldNames);
				if (!string.IsNullOrEmpty(text2))
				{
					string label = (((Object)(object)labelInput != (Object)null) ? labelInput.text : "");
					_dests.Add(new Destination(label, text2));
					Rebuild();
				}
			}, worldNames.Count > 0);
			win.AddLabel("Lock — " + LockStateText(), -182f, Place(30f, 10f), 340f, 30f, 18, GUIManager.Instance.ValheimOrange);
			InputField codeInput = win.AddInput("New code (blank = unchanged)", -182f, Place(36f, 10f), 310f, 36f, (ContentType)7);
			float y3 = Place(38f, 18f);
			win.AddButton("Set code", -260f, y3, 150f, 38f, delegate
			{
				string text2 = (((Object)(object)codeInput != (Object)null) ? codeInput.text : "");
				if (!string.IsNullOrEmpty(text2))
				{
					_codeMode = CodeMode.Set;
					_codeValue = text2;
					Rebuild();
				}
			});
			win.AddButton("Clear lock", -104f, y3, 150f, 38f, delegate
			{
				_codeMode = CodeMode.Clear;
				_codeValue = "";
				Rebuild();
			});
			win.AddButton("Save & Close", -182f, Place(42f, 26f), 200f, 42f, Save);
			float Place(float h, float gapAfter)
			{
				float result = cursor - h / 2f;
				cursor -= h + gapAfter;
				return result;
			}
		}

		private static void BuildRightColumn(HubWindow win, float height, string requirements, float reqHeight)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			float cursor = height / 2f - 74f;
			win.AddLabel("Portal storage — carry toll", 182f, Place(30f, 10f), 340f, 30f, 18, GUIManager.Instance.ValheimOrange);
			Text val = win.AddLabel(requirements, 182f, Place(reqHeight, 18f), 340f, reqHeight, 14, Color.white);
			if ((Object)(object)val != (Object)null)
			{
				val.alignment = (TextAnchor)0;
			}
			if (PortalStorage.Supports(_portal))
			{
				bool flag = PortalStorage.IsBuilder(_portal);
				win.AddButton(flag ? "Open portal storage" : "Storage — builder only", 182f, Place(38f, 26f), 240f, 38f, delegate
				{
					PortalStorage.Open(_portal);
				}, flag);
			}
			float Place(float h, float gapAfter)
			{
				float result = cursor - h / 2f;
				cursor -= h + gapAfter;
				return result;
			}
		}

		private static bool ShouldOpenStorage()
		{
			if (TollPolicy.Enabled && PortalStorage.Supports(_portal) && PortalStorage.IsBuilder(_portal))
			{
				return (Object)(object)Player.m_localPlayer != (Object)null;
			}
			return false;
		}

		private static string TollRequirementsText()
		{
			if (!PortalStorage.Supports(_portal))
			{
				return "This is a stone portal — it already carries every item, so it has no toll storage.";
			}
			return TollPolicy.RequirementsText();
		}

		private static float RequirementsHeight(string text)
		{
			int num = 1;
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\n')
				{
					num++;
				}
			}
			return Mathf.Clamp((float)num * 19f + 24f, 120f, 520f);
		}

		private static string LockStateText()
		{
			switch (_codeMode)
			{
			case CodeMode.Set:
				return "will be LOCKED (new code on save)";
			case CodeMode.Clear:
				return "will be UNLOCKED on save";
			default:
				if (!_lockedInitially)
				{
					return "not locked";
				}
				return "locked";
			}
		}

		private static void Save()
		{
			if ((Object)(object)_nview != (Object)null)
			{
				PortalData.SetInterServer(_nview, _enabled);
				PortalData.SetDestinations(_nview, _dests);
				if (_codeMode == CodeMode.Set)
				{
					PortalData.SetCode(_nview, _codeValue);
				}
				else if (_codeMode == CodeMode.Clear)
				{
					PortalData.SetCode(_nview, null);
				}
				PortalData.SetLinkMode(_nview, _linkMode);
				Plugin.Log.LogInfo((object)($"Portal config saved: enabled={_enabled}, link={_linkMode}, " + $"{_dests.Count} destination(s), code={_codeMode}."));
			}
			HubWindow.Close();
		}

		private static Dropdown CreateWorldDropdown(HubWindow win, float x, float y, List<string> worldNames)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = GUIManager.Instance.CreateDropDown(win.Panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), 16, 200f, 34f);
			obj.SetActive(true);
			Dropdown component = obj.GetComponent<Dropdown>();
			component.ClearOptions();
			component.AddOptions((worldNames.Count > 0) ? worldNames : new List<string> { "(no local worlds)" });
			return component;
		}

		private static string SelectedWorld(Dropdown dropdown, List<string> worldNames)
		{
			if ((Object)(object)dropdown == (Object)null || worldNames.Count == 0)
			{
				return "";
			}
			int value = dropdown.value;
			if (value < 0 || value >= worldNames.Count)
			{
				return "";
			}
			return worldNames[value];
		}

		private static List<string> LocalWorldNames()
		{
			List<string> list = new List<string>();
			try
			{
				List<World> worldList = SaveSystem.GetWorldList();
				if (worldList != null)
				{
					foreach (World item in worldList)
					{
						if (item != null && !item.m_menu && !string.IsNullOrEmpty(item.m_name) && !list.Contains(item.m_name))
						{
							list.Add(item.m_name);
						}
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"PortalConfigPanel: listing local worlds failed: {arg}");
			}
			return list;
		}
	}
	internal static class PortalNetworkMenu
	{
		internal static void Show(TeleportWorld source, List<NetPortal> portals)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			if (portals == null || portals.Count == 0)
			{
				((Character)localPlayer).Message((MessageType)2, "No other portals in your network yet. Set another portal to Network mode.", 0, (Sprite)null);
				return;
			}
			Vector3 here = ((Component)localPlayer).transform.position;
			portals.Sort(delegate(NetPortal a, NetPortal b)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//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_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_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val2 = a.Pos - here;
				float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
				val2 = b.Pos - here;
				return sqrMagnitude.CompareTo(((Vector3)(ref val2)).sqrMagnitude);
			});
			float num = 130f + (float)portals.Count * 52f;
			HubWindow hubWindow = HubWindow.Open("Travel to portal", 420f, num);
			if ((Object)(object)hubWindow == (Object)null)
			{
				return;
			}
			float num2 = num / 2f - 78f;
			for (int num3 = 0; num3 < portals.Count; num3++)
			{
				NetPortal target = portals[num3];
				Vector3 val = target.Pos - here;
				int num4 = Mathf.RoundToInt(((Vector3)(ref val)).magnitude);
				string arg = (string.IsNullOrEmpty(target.Tag) ? "unnamed portal" : target.Tag);
				string text = $"{arg}   ({num4}m)";
				hubWindow.AddButton(text, 0f, num2 - (float)num3 * 52f, 370f, 44f, delegate
				{
					NetworkController.Travel(source, target);
				});
			}
			hubWindow.AddButton("Cancel", 0f, (0f - num) / 2f + 34f, 150f, 38f, HubWindow.Close);
		}
	}
}
namespace InterServerPortal.Core
{
	internal static class DestinationValidator
	{
		internal enum Availability
		{
			Available,
			NoTarget,
			NotFound,
			BadVersion,
			Corrupt,
			Missin