Decompiled source of InterServerPortal v0.9.0

InterServerPortal.dll

Decompiled a day 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.9.0.0")]
[assembly: AssemblyInformationalVersion("0.9.0+4c1d71f34ca1ee168f078d6a144b3da5a24f1204")]
[assembly: AssemblyProduct("InterServerPortal")]
[assembly: AssemblyTitle("InterServerPortal")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.9.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.9.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.9.0";

		private Harmony _harmony;

		internal ConfigEntry<bool> DebugLogging;

		internal ConfigEntry<bool> RememberServerPassword;

		internal ConfigEntry<string> DiscordWebhookUrl;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: 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.");
			_harmony = new Harmony("com.interserverportal");
			_harmony.PatchAll();
			Log.LogInfo((object)"InterServerPortal v0.9.0 loaded (Phase 9 — per-mode portal glow + Discord travel notify).");
		}

		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);
		}

		[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 true;
			}
			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;
		}

		[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"));
			__result = __result + "\n" + Localization.instance.Localize(text + "\n" + text2);
		}
	}
	[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 int EnabledHash = StringExtensionMethods.GetStableHashCode("ISP.enabled");

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

		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 readonly Dictionary<long, int> _networkCounts = new Dictionary<long, int>();

		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();
			if (_networkCounts.TryGetValue(num, out var value))
			{
				return value >= 2;
			}
			return false;
		}

		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)
			{
				return;
			}
			_netCountAt = Time.time;
			_networkCounts.Clear();
			if (ZDOMan.instance == null)
			{
				return;
			}
			foreach (ZDO portal in ZDOMan.instance.GetPortals())
			{
				if (portal != null && portal.IsValid() && portal.GetInt(EnabledHash, 0) == 0 && portal.GetInt(LinkHash, 0) == 1)
				{
					long num = portal.GetLong(ZDOVars.s_creator, 0L);
					if (num != 0L)
					{
						_networkCounts.TryGetValue(num, out var value);
						_networkCounts[num] = value + 1;
					}
				}
			}
		}
	}
}
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;
			}
			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;
		}
	}
}
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 float RequestTimeout = 5f;

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

		private static ZRoutedRpc _registeredOn;

		private static Action<List<NetPortal>> _pending;

		private static float _pendingSince;

		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);
				_registeredOn = instance;
				_pending = null;
				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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: 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)
			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) && portal.GetLong(ZDOVars.s_creator, 0L) == playerId && portal.GetInt(LinkHash, 0) == 1)
				{
					list.Add(new NetPortal
					{
						Id = portal.m_uid,
						Pos = portal.GetPosition(),
						Rot = portal.GetRotation(),
						Tag = portal.GetString(ZDOVars.s_tag, "")
					});
				}
			}
			return list;
		}
	}
}
namespace InterServerPortal.Hub
{
	internal static class CodePrompt
	{
		internal static void Show(TeleportWorld portal, Action onSuccess)
		{
			//IL_007f: 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
	{
		internal static void BeginTravel(TeleportWorld portal)
		{
			if ((Object)(object)portal == (Object)null || HubWindow.IsOpen || 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)
			{
				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;

		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)
		{
			//IL_0036: 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.Build(title);
			_current = hubWindow;
			GUIManager.BlockInput(true);
			return hubWindow;
		}

		internal static void Close()
		{
			if (!((Object)(object)_current == (Object)null))
			{
				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)
		{
			//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_0038: 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)
			Panel = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), _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();
			}
		}

		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_001f: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_003c: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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)
			HubWindow.Close();
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)source == (Object)null))
			{
				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 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 Width = 520f;

		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;

		internal static void Show(TeleportWorld portal)
		{
			if (!((Object)(object)portal == (Object)null) && !((Object)(object)portal.m_nview == (Object)null))
			{
				_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_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0382: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: 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;
			HubWindow hubWindow = HubWindow.Open("Configure portal", 520f, num2);
			if ((Object)(object)hubWindow == (Object)null)
			{
				return;
			}
			float cursor = num2 / 2f - 74f;
			hubWindow.AddButton(_enabled ? "Inter-server portal: ON" : "Inter-server portal: OFF", 0f, Place(40f, 18f), 440f, 40f, delegate
			{
				_enabled = !_enabled;
				Rebuild();
			});
			string text = ((_linkMode == 1) ? "Same-world link: NETWORK (all my portals)" : "Same-world link: TAG pair (vanilla)");
			hubWindow.AddButton(text, 0f, Place(40f, 18f), 440f, 40f, delegate
			{
				_linkMode = ((_linkMode != 1) ? 1 : 0);
				Rebuild();
			});
			hubWindow.AddLabel("Destinations", 0f, Place(30f, 10f), 440f, 30f, 18, GUIManager.Instance.ValheimOrange);
			if (_dests.Count == 0)
			{
				hubWindow.AddLabel("(none yet — add one below)", 0f, Place(34f, 10f), 420f, 34f, 14, Color.gray);
			}
			else
			{
				foreach (Destination dest in _dests)
				{
					Destination captured = dest;
					float y = Place(34f, 10f);
					hubWindow.AddLabel(dest.Label + "  (" + dest.WorldName + ")", -90f, y, 250f, 34f, 15, Color.white);
					hubWindow.AddButton("Remove", 170f, y, 110f, 30f, delegate
					{
						_dests.Remove(captured);
						Rebuild();
					});
				}
			}
			cursor -= 8f;
			float y2 = Place(36f, 18f);
			InputField labelInput = hubWindow.AddInput("Label (optional)", -130f, y2, 220f, 36f, (ContentType)0);
			List<string> worldNames = LocalWorldNames();
			Dropdown dropdown = CreateWorldDropdown(hubWindow, 120f, y2, worldNames);
			hubWindow.AddButton("Add destination", 0f, Place(38f, 18f), 300f, 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);
			hubWindow.AddLabel("Lock — " + LockStateText(), 0f, Place(30f, 10f), 440f, 30f, 18, GUIManager.Instance.ValheimOrange);
			InputField codeInput = hubWindow.AddInput("New code (blank = unchanged)", 0f, Place(36f, 10f), 400f, 36f, (ContentType)7);
			float y3 = Place(38f, 18f);
			hubWindow.AddButton("Set code", -90f, y3, 180f, 38f, delegate
			{
				string text2 = (((Object)(object)codeInput != (Object)null) ? codeInput.text : "");
				if (!string.IsNullOrEmpty(text2))
				{
					_codeMode = CodeMode.Set;
					_codeValue = text2;
					Rebuild();
				}
			});
			hubWindow.AddButton("Clear lock", 90f, y3, 180f, 38f, delegate
			{
				_codeMode = CodeMode.Clear;
				_codeValue = "";
				Rebuild();
			});
			hubWindow.AddButton("Save & Close", 0f, Place(42f, 26f), 220f, 42f, Save);
			float Place(float h, float gapAfter)
			{
				float result = cursor - h / 2f;
				cursor -= h + gapAfter;
				return result;
			}
		}

		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_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: 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_00db: 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,
			MissingData,
			LoadError
		}

		internal struct Result
		{
			public Availability Status;

			public World World;

			public bool IsAvailable => Status == Availability.Available;

			public string Reason => Status switch
			{
				Availability.Available => "", 
				Availability.NoTarget => "no destination world configured", 
				Availability.NotFound => "world file not found", 
				Availability.BadVersion => "incompatible world version", 
				Availability.Corrupt => "world save is corrupt", 
				Availability.MissingData => "world save data is missing", 
				_ => "world could not be loaded", 
			};
		}

		internal static Result Validate(string worldName)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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)
			//IL_00e0: Expected I4, but got Unknown
			if (string.IsNullOrEmpty(worldName))
			{
				return new Result
				{
					Status = Availability.NoTarget
				};
			}
			World val = null;
			try
			{
				List<World> worldList = SaveSystem.GetWorldList();
				if (worldList != null)
				{
					foreach (World item in worldList)
					{
						if (item != null && !item.m_menu && string.Equals(item.m_name, worldName, StringComparison.OrdinalIgnoreCase))
						{
							val = item;
							break;
						}
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"DestinationValidator: reading world list failed: {arg}");
				return new Result
				{
					Status = Availability.LoadError
				};
			}
			if (val == null)
			{
				return new Result
				{
					Status = Availability.NotFound
				};
			}
			SaveDataError dataError = val.m_dataError;
			switch ((int)dataError)
			{
			case 0:
				return new Result
				{
					Status = Availability.Available,
					World = val
				};
			case 1:
				return new Result
				{
					Status = Availability.BadVersion
				};
			case 3:
				return new Result
				{
					Status = Availability.Corrupt
				};
			case 4:
			case 5:
				return new Result
				{
					Status = Availability.MissingData
				};
			default:
				return new Result
				{
					Status = Availability.LoadError
				};
			}
		}
	}
	internal static class ReturnRegistry
	{
		internal class Origin
		{
			public string Host;

			public int Port;

			public string Password;

			public string Backend;

			public string Timestamp;

			public bool IsValid
			{
				get
				{
					if (!string.IsNullOrEmpty(Host))
					{
						return Port > 0;
					}
					return false;
				}
			}
		}

		private static string FilePath => Path.Combine(Paths.ConfigPath, "InterServerPortal.return.txt");

		internal static bool HasOrigin => Load() != null;

		internal static void Save(Origin origin)
		{
			try
			{
				string text = (((Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.RememberServerPassword.Value) ? (origin.Password ?? "") : "");
				string[] contents = new string[5]
				{
					"host=" + origin.Host,
					"port=" + origin.Port,
					"password=" + text,
					"backend=" + origin.Backend,
					"timestamp=" + (origin.Timestamp ?? DateTime.UtcNow.ToString("o"))
				};
				File.WriteAllLines(FilePath, contents);
				Plugin.Debug($"ReturnRegistry saved: {origin.Host}:{origin.Port} (pw stored: {text.Length > 0})");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ReturnRegistry.Save failed: {arg}");
			}
		}

		internal static Origin Load()
		{
			try
			{
				if (!File.Exists(FilePath))
				{
					return null;
				}
				Origin origin = new Origin();
				string[] array = File.ReadAllLines(FilePath);
				foreach (string text in array)
				{
					int num = text.IndexOf('=');
					if (num >= 0)
					{
						string text2 = text.Substring(0, num);
						string text3 = text.Substring(num + 1);
						switch (text2)
						{
						case "host":
							origin.Host = text3;
							break;
						case "port":
						{
							int.TryParse(text3, out var result);
							origin.Port = result;
							break;
						}
						case "password":
							origin.Password = text3;
							break;
						case "backend":
							origin.Backend = text3;
							break;
						case "timestamp":
							origin.Timestamp = text3;
							break;
						}
					}
				}
				return origin.IsValid ? origin : null;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ReturnRegistry.Load failed: {arg}");
				return null;
			}
		}
	}
	internal static class WorldSwitcher
	{
		internal enum SwitchKind
		{
			None,
			ToLocal,
			ToServer
		}

		internal class PendingSwitch
		{
			public SwitchKind Kind;

			public string LocalWorldName;

			public ReturnRegistry.Origin ServerOrigin;
		}

		internal static PendingSwitch Pending;

		internal static bool InProgress;

		internal static bool PortalArmed;

		internal static bool AwaitingArrival;

		internal static void RequestSwitchToLocal(string localWorldName)
		{
			if (!Guard())
			{
				return;
			}
			if (IsLocalHost() && string.Equals((ZNet.World != null) ? ZNet.World.m_name : "", localWorldName, StringComparison.OrdinalIgnoreCase))
			{
				Plugin.Log.LogInfo((object)"Already in that local world; ignoring switch-to-local.");
				return;
			}
			DestinationValidator.Result result = DestinationValidator.Validate(localWorldName);
			if (!result.IsAvailable)
			{
				string text = "'" + localWorldName + "' is unavailable: " + result.Reason + ".";
				Plugin.Log.LogWarning((object)("Switch to local blocked — " + text));
				Notify(text);
				return;
			}
			if (IsRemoteClient())
			{
				ReturnRegistry.Origin origin = CaptureCurrentServerOrigin();
				if (origin != null && origin.IsValid)
				{
					ReturnRegistry.Save(origin);
				}
				else
				{
					Plugin.Log.LogWarning((object)"Could not capture current server address — the return trip may not work. (Are you connected to a dedicated server via IP?)");
				}
			}
			if (IsRemoteClient())
			{
				string playerName = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "");
				string fromServer = ((ZNet.World != null) ? ZNet.World.m_name : "");
				DiscordNotifier.NotifyTravelToLocal(playerName, localWorldName, fromServer);
			}
			InProgress = true;
			Pending = new PendingSwitch
			{
				Kind = SwitchKind.ToLocal,
				LocalWorldName = localWorldName
			};
			Plugin.Log.LogInfo((object)("Switching to local world '" + localWorldName + "' …"));
			Leave();
		}

		internal static void RequestReturnToServer()
		{
			if (!Guard())
			{
				return;
			}
			if (IsRemoteClient())
			{
				Plugin.Log.LogInfo((object)"Already connected to a server; ignoring return-to-server.");
				return;
			}
			ReturnRegistry.Origin origin = ReturnRegistry.Load();
			if (origin == null)
			{
				Plugin.Log.LogWarning((object)"RequestReturnToServer: no saved origin server to return to.");
				return;
			}
			InProgress = true;
			Pending = new PendingSwitch
			{
				Kind = SwitchKind.ToServer,
				ServerOrigin = origin
			};
			Plugin.Log.LogInfo((object)$"Returning to server {origin.Host}:{origin.Port} …");
			Leave();
		}

		internal static IEnumerator ResumePending(FejdStartup fejd)
		{
			PendingSwitch pending = Pending;
			Pending = null;
			if (pending == null)
			{
				yield break;
			}
			for (int i = 0; i < 10; i++)
			{
				if (!((Object)(object)FejdStartup.instance == (Object)null))
				{
					break;
				}
				yield return null;
			}
			fejd = FejdStartup.instance;
			if ((Object)(object)fejd == (Object)null)
			{
				Plugin.Log.LogError((object)"ResumePending: FejdStartup.instance never appeared; aborting switch.");
				InProgress = false;
				yield break;
			}
			yield return null;
			yield return null;
			switch (pending.Kind)
			{
			case SwitchKind.ToLocal:
				StartLocalWorld(fejd, pending.LocalWorldName);
				break;
			case SwitchKind.ToServer:
				JoinServer(fejd, pending.ServerOrigin);
				break;
			}
		}

		private static void Leave()
		{
			PortalArmed = false;
			AwaitingArrival = true;
			if ((Object)(object)Game.instance != (Object)null)
			{
				Game.instance.Logout(true, true);
				return;
			}
			Plugin.Log.LogError((object)"Leave: Game.instance is null; cannot log out.");
			InProgress = false;
			Pending = null;
		}

		private static void StartLocalWorld(FejdStartup fejd, string worldName)
		{
			try
			{
				DestinationValidator.Result result = DestinationValidator.Validate(worldName);
				if (!result.IsAvailable)
				{
					Plugin.Log.LogError((object)("StartLocalWorld: '" + worldName + "' unavailable mid-flight (" + result.Reason + "). Falling back to the origin server."));
					FallbackToOrigin(fejd);
				}
				else
				{
					World val = (fejd.m_world = result.World);
					fejd.m_startingWorld = true;
					ZNet.SetServer(true, false, false, val.m_name, "", val);
					fejd.TransitionToMainScene();
					Plugin.Log.LogInfo((object)("Loading local world '" + val.m_name + "' …"));
					InProgress = false;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"StartLocalWorld failed: {arg}");
				FallbackToOrigin(fejd);
			}
		}

		private static void FallbackToOrigin(FejdStartup fejd)
		{
			ReturnRegistry.Origin origin = ReturnRegistry.Load();
			if (origin != null && origin.IsValid)
			{
				Plugin.Log.LogWarning((object)$"Falling back — reconnecting to origin server {origin.Host}:{origin.Port}.");
				JoinServer(fejd, origin);
			}
			else
			{
				Plugin.Log.LogError((object)"Fallback failed: no saved origin server to return to. Staying on the main menu.");
				InProgress = false;
			}
		}

		private static void JoinServer(FejdStartup fejd, ReturnRegistry.Origin origin)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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)
			try
			{
				OnlineBackendType val = ParseBackend(origin.Backend);
				NormalizeHostPort(origin.Host, origin.Port, out var host, out var port);
				ZNet.SetServer(false, false, false, "", "", (World)null);
				ZNet.ResetServerHost();
				ZNet.SetServerHost(host, port, val);
				if (!string.IsNullOrEmpty(origin.Password))
				{
					FejdStartup.ServerPassword = origin.Password;
				}
				fejd.m_startingWorld = false;
				fejd.TransitionToMainScene();
				Plugin.Log.LogInfo((object)$"Connecting to host='{host}' port={port} (backend {val}) …");
				InProgress = false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"JoinServer failed: {arg}");
				InProgress = false;
			}
		}

		private static OnlineBackendType ParseBackend(string name)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(name) || !Enum.TryParse<OnlineBackendType>(name, out OnlineBackendType result) || (int)result == 4)
			{
				return (OnlineBackendType)0;
			}
			return result;
		}

		private static bool Guard()
		{
			if (InProgress)
			{
				Plugin.Log.LogWarning((object)"A world switch is already in progress; ignoring.");
				return false;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				Plugin.Log.LogWarning((object)"No local player; not in a playable session.");
				return false;
			}
			return true;
		}

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

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

		private static ReturnRegistry.Origin CaptureCurrentServerOrigin()
		{
			NormalizeHostPort(ZNet.m_serverHost, ZNet.m_serverHostPort, out var host, out var port);
			if (string.IsNullOrEmpty(host) || port <= 0)
			{
				return null;
			}
			return new ReturnRegistry.Origin
			{
				Host = host,
				Port = port,
				Password = (ZNet.m_serverPassword ?? ""),
				Backend = ((object)Unsafe.As<OnlineBackendType, OnlineBackendType>(ref ZNet.m_onlineBackend)/*cast due to .constrained prefix*/).ToString(),
				Timestamp = DateTime.UtcNow.ToString("o")
			};
		}

		internal static void NormalizeHostPort(string rawHost, int rawPort, out string host, out int port)
		{
			host = rawHost ?? "";
			port = rawPort;
			int num = host.LastIndexOf(':');
			int num2 = host.IndexOf(':');
			bool flag = num2 >= 0 && num2 != num && !host.Contains("]");
			if (num > 0 && !flag && int.TryParse(host.Substring(num + 1), out var result) && result > 0)
			{
				host = host.Substring(0, num);
				if (port <= 0)
				{
					port = result;
				}
			}
		}

		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);
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}