Decompiled source of PortalLimit v0.1.4

BepInEx\config\AzuAntiCheat_Whitelist\PortalLimitClient.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace PortalLimit.Client;

[BepInPlugin("keith.valheim.portallimit.client", "Portal Limit and Discovery Client", "0.1.4")]
public class PortalLimitClientPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Chat), "InputText")]
	private static class ChatInputPatch
	{
		private static bool Prefix(Chat __instance)
		{
			string a = (((Object)(object)((Terminal)__instance).m_input != (Object)null) ? ((TMP_InputField)((Terminal)__instance).m_input).text : string.Empty);
			if (string.Equals(a, "/portals", StringComparison.OrdinalIgnoreCase))
			{
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.RequestCountForChat();
				}
				if ((Object)(object)((Terminal)__instance).m_input != (Object)null)
				{
					((TMP_InputField)((Terminal)__instance).m_input).text = string.Empty;
				}
				__instance.Hide();
				return false;
			}
			if (!string.Equals(a, "/portallimit", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if ((Object)(object)Instance != (Object)null)
			{
				Instance.ToggleAdminWindow();
			}
			if ((Object)(object)((Terminal)__instance).m_input != (Object)null)
			{
				((TMP_InputField)((Terminal)__instance).m_input).text = string.Empty;
			}
			__instance.Hide();
			return false;
		}
	}

	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	private static class PlacePiecePatch
	{
		private static bool Prefix(Piece piece, ref bool __result)
		{
			if (!WouldExceedPortalLimit(piece))
			{
				return true;
			}
			__result = false;
			Instance.ShowMessage("Portal limit reached: " + Instance.maxPortalsPerPlayer + " portal(s) per player.");
			Instance.RequestCount();
			return false;
		}

		private static void Postfix(Piece piece, bool __result)
		{
			if (__result && (Object)(object)Instance != (Object)null && (Object)(object)piece != (Object)null && Instance.IsLimitedPortal(piece))
			{
				Instance.ReportPortalMetadata(piece, "placed");
				Instance.RequestPlacementCountSoon();
			}
		}
	}

	[HarmonyPatch(typeof(Piece), "CanBeRemoved")]
	private static class PieceCanBeRemovedPatch
	{
		private static void Postfix(Piece __instance, ref bool __result)
		{
			if (__result && !((Object)(object)Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && Instance.IsLimitedPortal(__instance))
			{
				long creator = __instance.GetCreator();
				long localPlayerId = GetLocalPlayerId();
				if (!Instance.isAdmin && creator != 0 && creator != localPlayerId)
				{
					__result = false;
					Instance.ShowMessage("Only the builder or an admin can remove this portal.");
				}
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Interact")]
	private static class TeleportWorldInteractPatch
	{
		private static bool Prefix(TeleportWorld __instance, Humanoid human, bool hold, ref bool __result)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)human != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (Instance.replayingVanillaPortalInteract)
			{
				return true;
			}
			if (Instance.isAdmin && Instance.adminHoldEToEdit && !hold)
			{
				Instance.BeginDelayedPortalInteract(__instance);
				__result = false;
				return false;
			}
			if (hold && Instance.isAdmin && Instance.adminHoldEToEdit)
			{
				Instance.OpenAdminWindowForPortal(__instance);
				Instance.ResetHoldInteract();
				__result = false;
				return false;
			}
			if (!hold && ShouldBlockPortalUse(__instance))
			{
				__result = false;
				return false;
			}
			return true;
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Teleport")]
	private static class TeleportWorldTeleportPatch
	{
		private static bool Prefix(TeleportWorld __instance, Player player)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			bool flag = !ShouldBlockBlankPortalUse(__instance) && !ShouldBlockPortalUse(__instance);
			if (flag && (Object)(object)Instance != (Object)null)
			{
				Instance.ReportPortalMetadata(__instance, "used");
			}
			return flag;
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "GetHoverText")]
	private static class TeleportWorldHoverTextPatch
	{
		private static void Postfix(TeleportWorld __instance, ref string __result)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return;
			}
			if (string.IsNullOrWhiteSpace(GetPortalTag(__instance)) && !string.IsNullOrEmpty(__result))
			{
				__result = __result.Replace(" [CONNECTED]", string.Empty).Replace("[CONNECTED]", string.Empty);
			}
			if (Instance.isAdmin)
			{
				if (Instance.adminHoldEToEdit)
				{
					__result += "\n<color=yellow>Hold E: Portal Limit settings</color>";
				}
				string portalCreatorName = GetPortalCreatorName(__instance);
				if (!string.IsNullOrWhiteSpace(portalCreatorName))
				{
					__result = __result + "\n<color=#ffd27a>Built by: " + portalCreatorName + "</color>";
				}
			}
			if (!Instance.discoveryEnabled || (!Instance.showDiscoveryHoverText && !Instance.isAdmin))
			{
				return;
			}
			int discoveryRole = GetDiscoveryRole(__instance);
			if (discoveryRole == 0)
			{
				return;
			}
			string discoveryLink = GetDiscoveryLink(__instance);
			if (string.IsNullOrWhiteSpace(discoveryLink))
			{
				return;
			}
			string text = string.Empty;
			if (Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryUnlockedHoverText");
				if (string.IsNullOrWhiteSpace(text))
				{
					text = Instance.discoveryUnlockedHoverText;
				}
			}
			else
			{
				switch (discoveryRole)
				{
				case 1:
					text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryLockedHoverText");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = Instance.discoveryLockedHoverText;
					}
					break;
				case 2:
					text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryRemoteHoverText");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = Instance.discoveryRemoteHoverText;
					}
					break;
				}
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				__result = __result + "\n<color=orange>" + text + "</color>";
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "TargetFound")]
	private static class TeleportWorldTargetFoundPatch
	{
		private static void Postfix(TeleportWorld __instance, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				__result = false;
				SetTargetFound(__instance, value: false);
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "HaveTarget")]
	private static class TeleportWorldHaveTargetPatch
	{
		private static void Postfix(TeleportWorld __instance, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				__result = false;
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Update")]
	private static class TeleportWorldVisualPatch
	{
		private static void Postfix(TeleportWorld __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				PortalLimitDiscoveryVisual portalLimitDiscoveryVisual = ((Component)__instance).GetComponent<PortalLimitDiscoveryVisual>();
				if ((Object)(object)portalLimitDiscoveryVisual == (Object)null)
				{
					portalLimitDiscoveryVisual = ((Component)__instance).gameObject.AddComponent<PortalLimitDiscoveryVisual>();
				}
				portalLimitDiscoveryVisual.Refresh(__instance);
			}
		}
	}

	private class PortalLimitDiscoveryVisual : MonoBehaviour
	{
		private GameObject markerRoot;

		private TextMesh markerText;

		private Renderer[] portalRenderers;

		private ParticleSystem[] portalParticles;

		private Light[] portalLights;

		private readonly Dictionary<Renderer, Color> originalColors = new Dictionary<Renderer, Color>();

		private readonly Dictionary<ParticleSystem, bool> originalParticleStates = new Dictionary<ParticleSystem, bool>();

		private readonly Dictionary<Light, bool> originalLightStates = new Dictionary<Light, bool>();

		internal void Refresh(TeleportWorld portal)
		{
			//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_009c: 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)
			if (!((Object)(object)Instance == (Object)null) && !((Object)(object)portal == (Object)null))
			{
				bool flag = Instance.ShouldShowDiscoveryWorldMarker(portal);
				bool enabled = Instance.ShouldTintDiscoveryPortal(portal);
				bool suppress = Instance.ShouldSuppressLockedPortalGlow(portal);
				Color discoveryVisualColor = Instance.GetDiscoveryVisualColor(portal);
				EnsureMarker();
				markerRoot.SetActive(flag);
				if (flag)
				{
					SetMarkerWorldPose(portal);
					SetMarkerText(Instance.GetDiscoveryMarkerDisplayText(portal));
					SetMarkerColor(discoveryVisualColor);
				}
				UpdatePortalTint(enabled, discoveryVisualColor);
				UpdatePortalEffects(suppress);
			}
		}

		private void EnsureMarker()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: 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)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)markerRoot != (Object)null))
			{
				markerRoot = new GameObject("PortalLimit_DiscoveryMarker");
				markerRoot.transform.SetParent(((Component)this).transform, false);
				markerRoot.transform.localPosition = new Vector3(0f, 1.55f, 0f);
				markerRoot.transform.localRotation = Quaternion.identity;
				markerRoot.transform.localScale = Vector3.one;
				markerText = markerRoot.AddComponent<TextMesh>();
				markerText.text = "UNLOCK ON OTHER SIDE";
				markerText.anchor = (TextAnchor)4;
				markerText.alignment = (TextAlignment)1;
				markerText.fontSize = 96;
				markerText.characterSize = 0.01f;
				markerText.richText = false;
				SetMarkerColor(LockedOrange);
				markerRoot.SetActive(false);
			}
		}

		private void SetMarkerText(string text)
		{
			if (!((Object)(object)markerText == (Object)null))
			{
				markerText.text = text;
				if (string.Equals(text, "X", StringComparison.Ordinal))
				{
					markerText.fontSize = 128;
					markerText.characterSize = 0.03f;
				}
				else if (string.Equals(text, "LOCKED", StringComparison.Ordinal))
				{
					markerText.fontSize = 96;
					markerText.characterSize = 0.018f;
				}
				else
				{
					markerText.fontSize = 96;
					markerText.characterSize = 0.01f;
				}
			}
		}

		private void SetMarkerColor(Color color)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)markerText != (Object)null)
			{
				markerText.color = color;
			}
		}

		private void SetMarkerWorldPose(TeleportWorld portal)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: 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_0069: 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_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)markerRoot == (Object)null || (Object)(object)portal == (Object)null)
			{
				return;
			}
			Vector3 val = ((Component)portal).transform.position + Vector3.up * 1.55f;
			if ((Object)(object)Camera.main != (Object)null)
			{
				Vector3 val2 = ((Component)Camera.main).transform.position - val;
				if (((Vector3)(ref val2)).sqrMagnitude > 0.001f)
				{
					markerRoot.transform.position = val + ((Vector3)(ref val2)).normalized * 0.45f;
					markerRoot.transform.rotation = Quaternion.LookRotation(-((Vector3)(ref val2)).normalized, Vector3.up);
					return;
				}
			}
			markerRoot.transform.position = val;
			markerRoot.transform.rotation = ((Component)portal).transform.rotation;
		}

		private void UpdatePortalTint(bool enabled, Color color)
		{
			//IL_009b: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			if (portalRenderers == null)
			{
				portalRenderers = ((Component)this).GetComponentsInChildren<Renderer>(true);
			}
			Renderer[] array = portalRenderers;
			foreach (Renderer val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalColors.ContainsKey(val))
					{
						originalColors[val] = (val.material.HasProperty("_Color") ? val.material.color : Color.white);
					}
					if (val.material.HasProperty("_Color"))
					{
						val.material.color = (enabled ? Color.Lerp(originalColors[val], color, 0.45f) : originalColors[val]);
					}
				}
			}
		}

		private void UpdatePortalEffects(bool suppress)
		{
			ParticleSystem[] array;
			if (portalParticles == null)
			{
				portalParticles = ((Component)this).GetComponentsInChildren<ParticleSystem>(true);
				array = portalParticles;
				foreach (ParticleSystem val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						originalParticleStates[val] = ((Component)val).gameObject.activeSelf;
					}
				}
			}
			Light[] array2;
			if (portalLights == null)
			{
				portalLights = ((Component)this).GetComponentsInChildren<Light>(true);
				array2 = portalLights;
				foreach (Light val2 in array2)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						originalLightStates[val2] = ((Behaviour)val2).enabled;
					}
				}
			}
			array = portalParticles;
			foreach (ParticleSystem val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalParticleStates.TryGetValue(val, out var value))
					{
						value = true;
					}
					((Component)val).gameObject.SetActive(!suppress && value);
					if (!suppress && value && !val.isPlaying)
					{
						val.Play(true);
					}
				}
			}
			array2 = portalLights;
			foreach (Light val2 in array2)
			{
				if (!((Object)(object)val2 == (Object)null) && !((Component)val2).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalLightStates.TryGetValue(val2, out var value2))
					{
						value2 = true;
					}
					((Behaviour)val2).enabled = !suppress && value2;
				}
			}
		}
	}

	public const string PluginGuid = "keith.valheim.portallimit.client";

	public const string PluginName = "Portal Limit and Discovery Client";

	public const string PluginVersion = "0.1.4";

	private const string RpcConfigRequest = "keith.valheim.portallimit.config_request";

	private const string RpcConfigResponse = "keith.valheim.portallimit.config_response";

	private const string RpcConfigUpdate = "keith.valheim.portallimit.config_update";

	private const string RpcCountRequest = "keith.valheim.portallimit.count_request";

	private const string RpcCountResponse = "keith.valheim.portallimit.count_response";

	private const string RpcNotice = "keith.valheim.portallimit.notice";

	private const string RpcDiscoveryMarkRequest = "keith.valheim.portallimit.discovery_mark_request";

	private const string RpcDiscoveryUnlockRequest = "keith.valheim.portallimit.discovery_unlock_request";

	private const string RpcDiscoveryRelockRequest = "keith.valheim.portallimit.discovery_relock_request";

	private const string RpcDiscoveryUnlockListRequest = "keith.valheim.portallimit.discovery_unlock_list_request";

	private const string RpcDiscoveryUnlockListResponse = "keith.valheim.portallimit.discovery_unlock_list_response";

	private const string RpcAdminReportRequest = "keith.valheim.portallimit.admin_report_request";

	private const string RpcAdminReportResponse = "keith.valheim.portallimit.admin_report_response";

	private const string RpcPortalMetadataUpdate = "keith.valheim.portallimit.portal_metadata_update";

	private const string DiscoveryRoleKey = "PortalLimit_DiscoveryRole";

	private const string DiscoveryLinkKey = "PortalLimit_DiscoveryLink";

	private const string DiscoveryColorKey = "PortalLimit_DiscoveryColor";

	private const string DiscoveryMarkerTextKey = "PortalLimit_DiscoveryMarkerText";

	private const string DiscoveryShowMarkerKey = "PortalLimit_DiscoveryShowMarker";

	private const string DiscoverySuppressGlowKey = "PortalLimit_DiscoverySuppressGlow";

	private const string DiscoveryLockedHoverTextKey = "PortalLimit_DiscoveryLockedHoverText";

	private const string DiscoveryRemoteHoverTextKey = "PortalLimit_DiscoveryRemoteHoverText";

	private const string DiscoveryUnlockedHoverTextKey = "PortalLimit_DiscoveryUnlockedHoverText";

	private const string PortalBuiltUtcKey = "PortalLimit_BuiltUtc";

	private const int DiscoveryRoleNone = 0;

	private const int DiscoveryRoleLocked = 1;

	private const int DiscoveryRoleRemote = 2;

	internal static PortalLimitClientPlugin Instance;

	internal static ManualLogSource Log;

	private static readonly FieldInfo AllPiecesField = AccessTools.Field(typeof(Piece), "s_allPieces");

	private static readonly FieldInfo TeleportWorldTargetFoundField = AccessTools.Field(typeof(TeleportWorld), "m_target_found");

	private static readonly MethodInfo GetPlayerIdMethod = AccessTools.Method(typeof(Player), "GetPlayerID", (Type[])null, (Type[])null);

	private static readonly MethodInfo GetPrefabNameMethod = AccessTools.Method(typeof(ZNetView), "GetPrefabName", (Type[])null, (Type[])null);

	private static readonly Color LockedOrange = new Color(1f, 0.6235294f, 0.1019608f, 1f);

	private static readonly string[] MarkerColorLabels = new string[8] { "RED", "ORANGE", "YELLOW", "GREEN", "BLUE", "PURPLE", "WHITE", "BLACK" };

	private static readonly string[] MarkerColorValues = new string[8] { "#ff3030", "#ff9f1a", "#ffd84a", "#42ff68", "#38a6ff", "#b05cff", "#ffffff", "#000000" };

	private static readonly string[] PortalRoleChoiceLabels = new string[3] { "NORMAL PORTAL", "LOCKED ENTRANCE", "UNLOCKED ENTRANCE" };

	private static readonly string[] MarkerTextLabels = new string[3] { "UNLOCK ON OTHER SIDE", "LOCKED", "X" };

	private static readonly string[] MarkerTextValues = new string[3] { "UnlockOnOtherSide", "Locked", "X" };

	private Harmony harmony;

	private bool rpcRegistered;

	private bool configRequested;

	private bool showCountMessageOnNextResponse;

	private bool showPlacementCountOnNextResponse;

	private bool openAdminUiAfterPermissionCheck;

	private bool adminStatusKnown;

	private bool isAdmin;

	private bool adminBypass = true;

	private bool enforceOnServer = true;

	private int maxPortalsPerPlayer = 10;

	private string portalPrefabNames = "portal_wood,portal_stone";

	private bool discoveryEnabled = true;

	private bool discoveryAdminBypass = true;

	private bool adminHoldEToEdit = true;

	private bool showDiscoveryHoverText = true;

	private string discoveryLockedHoverText = "Locked: find the other end first.";

	private string discoveryRemoteHoverText = "Discovery portal: use this side to unlock the route.";

	private string discoveryUnlockedHoverText = "Discovery route unlocked.";

	private string discoveryLockedMessage = "This portal is locked. Find and enter the other end first.";

	private string discoveryUnlockMessage = "Discovery portal unlocked.";

	private bool discoveryShowWorldMarker = true;

	private bool discoveryTintPortalModel;

	private bool discoverySuppressLockedGlow;

	private string discoveryLockedColor = "#ff9f1a";

	private string discoveryRemoteColor = "#38a6ff";

	private string discoveryUnlockedColor = "#42ff68";

	private readonly HashSet<string> unlockedDiscoveryLinks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private int serverPortalCount;

	private Rect adminWindow = new Rect(120f, 60f, 700f, 720f);

	private Vector2 adminScroll;

	private Vector2 portalInfoThisScroll;

	private Vector2 portalInfoWorldScroll;

	private bool showAdminWindow;

	private int adminTab;

	private string openDropdown = string.Empty;

	private bool portalInfoShowThis;

	private bool portalInfoShowWorld;

	private string portalInfoThisText = "Open this tab while editing a portal.";

	private string portalInfoWorldText = "Click Refresh to load world portal information.";

	private string portalInfoRequestedPortalKey = string.Empty;

	private int selectedPortalRole;

	private string loadedPortalKey = string.Empty;

	private TeleportWorld selectedPortal;

	private string maxText = "10";

	private string portalNamesText = "portal_wood,portal_stone";

	private string discoveryLinkText = string.Empty;

	private string selectedPortalColorText = "#ff9f1a";

	private int selectedPortalColorIndex;

	private string selectedPortalMarkerText = "UNLOCK ON OTHER SIDE";

	private bool selectedPortalShowMarker = true;

	private bool selectedPortalSuppressGlow;

	private string selectedLockedHoverText = string.Empty;

	private string selectedRemoteHoverText = string.Empty;

	private string selectedUnlockedHoverText = string.Empty;

	private string discoveryLockedHoverTextField = "Locked: find the other end first.";

	private string discoveryRemoteHoverTextField = "Discovery portal: use this side to unlock the route.";

	private string discoveryUnlockedHoverTextField = "Discovery route unlocked.";

	private string discoveryLockedMessageField = "This portal is locked. Find and enter the other end first.";

	private string discoveryUnlockMessageField = "Discovery portal unlocked.";

	private int selectedPortalMarkerTextIndex;

	private TeleportWorld holdInteractPortal;

	private float holdInteractStartedAt;

	private bool holdInteractOpened;

	private bool replayingVanillaPortalInteract;

	private string discoveryLockedColorField = "#ff9f1a";

	private string discoveryRemoteColorField = "#38a6ff";

	private string discoveryUnlockedColorField = "#42ff68";

	private string statusMessage = "Use /portallimit to load server settings.";

	private bool adminStylesReady;

	private Texture2D adminWindowTexture;

	private Texture2D adminHeaderTexture;

	private Texture2D adminSectionTexture;

	private Texture2D adminInputTexture;

	private Texture2D adminButtonTexture;

	private Texture2D adminButtonHoverTexture;

	private Texture2D adminTabTexture;

	private Texture2D adminTabSelectedTexture;

	private GUIStyle adminWindowStyle;

	private GUIStyle adminTitleStyle;

	private GUIStyle adminLabelStyle;

	private GUIStyle adminHelpStyle;

	private GUIStyle adminSectionStyle;

	private GUIStyle adminButtonStyle;

	private GUIStyle adminTabStyle;

	private GUIStyle adminTabSelectedStyle;

	private GUIStyle adminTextFieldStyle;

	private GUIStyle adminBoxStyle;

	private ConfigEntry<KeyCode> toggleAdminUiKey;

	private void Awake()
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Expected O, but got Unknown
		if (IsDedicatedServerProcess())
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Portal Limit Client disabled on the dedicated server process.");
			((Behaviour)this).enabled = false;
			return;
		}
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		toggleAdminUiKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "ToggleAdminUIKey", (KeyCode)291, "Key used to open the portal limit admin UI.");
		harmony = new Harmony("keith.valheim.portallimit.client");
		harmony.PatchAll();
		RegisterConsoleCommands();
		((MonoBehaviour)this).InvokeRepeating("TryRegisterRpcs", 1f, 1f);
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		DestroyAdminTexture(adminWindowTexture);
		DestroyAdminTexture(adminHeaderTexture);
		DestroyAdminTexture(adminSectionTexture);
		DestroyAdminTexture(adminInputTexture);
		DestroyAdminTexture(adminButtonTexture);
		DestroyAdminTexture(adminButtonHoverTexture);
		DestroyAdminTexture(adminTabTexture);
		DestroyAdminTexture(adminTabSelectedTexture);
	}

	private static bool IsDedicatedServerProcess()
	{
		string processName = Process.GetCurrentProcess().ProcessName;
		return processName.StartsWith("valheim_server", StringComparison.OrdinalIgnoreCase);
	}

	private void TryRegisterRpcs()
	{
		if (!rpcRegistered && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.config_response", (Action<long, string>)OnConfigResponse);
			ZRoutedRpc.instance.Register<int, int, bool, string>("keith.valheim.portallimit.count_response", (Method<int, int, bool, string>)OnCountResponse);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.notice", (Action<long, string>)OnNotice);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.discovery_unlock_list_response", (Action<long, string>)OnDiscoveryUnlockListResponse);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.admin_report_response", (Action<long, string>)OnAdminReportResponse);
			rpcRegistered = true;
			((MonoBehaviour)this).CancelInvoke("TryRegisterRpcs");
		}
	}

	private static void RegisterConsoleCommands()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand("portallimit", "Portal Limit commands. Use: portallimit, portallimit count, portallimit counts, portallimit portals, portallimit export", new ConsoleEvent(OnConsolePortalLimit), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}

	private static void OnConsolePortalLimit(ConsoleEventArgs args)
	{
		if ((Object)(object)Instance == (Object)null)
		{
			Terminal.Log((object)"Portal Limit client is not ready.");
			return;
		}
		string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "help");
		if (text == "help")
		{
			Terminal.Log((object)"Portal Limit F5 commands:");
			Terminal.Log((object)"  portallimit count   - show your portal count");
			Terminal.Log((object)"  portallimit counts  - admin: show counts for all players");
			Terminal.Log((object)"  portallimit portals - admin: list known portals with names and coordinates");
			Terminal.Log((object)"  portallimit export  - admin: rewrite report files on the server");
			Terminal.Log((object)"Regular players can also type /portals in normal chat.");
		}
		else if (text == "count")
		{
			Instance.showCountMessageOnNextResponse = true;
			Instance.RequestCount();
			Terminal.Log((object)"Portal Limit: checking your portal count...");
		}
		else if (text != "counts" && text != "portals" && text != "export")
		{
			Terminal.Log((object)"Unknown Portal Limit command. Use: portallimit");
		}
		else if (ZRoutedRpc.instance == null)
		{
			Terminal.Log((object)"Portal Limit server RPC is not ready yet.");
		}
		else
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.admin_report_request", new object[1] { text });
			Terminal.Log((object)("Portal Limit: requesting " + text + " report..."));
		}
	}

	private void Update()
	{
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		if (rpcRegistered && !((Object)(object)ZNet.instance == (Object)null))
		{
			if (!configRequested && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				RequestConfig(openUi: false);
			}
			if (Input.GetKeyDown(toggleAdminUiKey.Value))
			{
				ToggleAdminWindow();
			}
			UpdateHoldInteractAdminOpen();
		}
	}

	private void UpdateHoldInteractAdminOpen()
	{
		if (!isAdmin || !adminHoldEToEdit || showAdminWindow || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			ResetHoldInteract();
		}
		else if ((Object)(object)holdInteractPortal == (Object)null)
		{
			TeleportWorld hoveredPortal = GetHoveredPortal();
			if ((Object)(object)hoveredPortal != (Object)null && Input.GetKey((KeyCode)101))
			{
				BeginDelayedPortalInteract(hoveredPortal);
			}
		}
		else if (!Input.GetKey((KeyCode)101))
		{
			TeleportWorld val = holdInteractPortal;
			bool flag = !holdInteractOpened && Time.unscaledTime - holdInteractStartedAt < 0.45f;
			ResetHoldInteract();
			if (flag && (Object)(object)val != (Object)null)
			{
				ReplayVanillaPortalInteract(val);
			}
		}
		else if (!holdInteractOpened && Time.unscaledTime - holdInteractStartedAt >= 0.45f)
		{
			holdInteractOpened = true;
			OpenAdminWindowForPortal(holdInteractPortal);
		}
	}

	private void BeginDelayedPortalInteract(TeleportWorld portal)
	{
		holdInteractPortal = portal;
		holdInteractStartedAt = Time.unscaledTime;
		holdInteractOpened = false;
	}

	private void ResetHoldInteract()
	{
		holdInteractPortal = null;
		holdInteractStartedAt = 0f;
		holdInteractOpened = false;
	}

	private void ReplayVanillaPortalInteract(TeleportWorld portal)
	{
		if ((Object)(object)portal == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		replayingVanillaPortalInteract = true;
		try
		{
			portal.Interact((Humanoid)(object)Player.m_localPlayer, false, false);
		}
		finally
		{
			replayingVanillaPortalInteract = false;
		}
	}

	private void OnGUI()
	{
		//IL_0025: 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_0045: Expected O, but got Unknown
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		if (showAdminWindow)
		{
			EnsureAdminStyles();
			CenterAdminWindowIfNeeded();
			adminWindow = GUI.Window(((Object)this).GetInstanceID(), adminWindow, new WindowFunction(DrawAdminWindow), GUIContent.none, GUIStyle.none);
		}
	}

	private void CenterAdminWindowIfNeeded()
	{
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		float num = Mathf.Min(760f, (float)Screen.width - 40f);
		float num2 = Mathf.Min(680f, (float)Screen.height - 40f);
		if (Math.Abs(((Rect)(ref adminWindow)).width - num) > 0.1f || Math.Abs(((Rect)(ref adminWindow)).height - num2) > 0.1f)
		{
			adminWindow = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2);
		}
	}

	private void DrawAdminWindow(int id)
	{
		//IL_0028: 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_008e: 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)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Unknown result type (might be due to invalid IL or missing references)
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		EnsureAdminStyles();
		GUI.Box(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, ((Rect)(ref adminWindow)).height), GUIContent.none, adminWindowStyle);
		GUI.Box(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, 42f), GUIContent.none, adminSectionStyle);
		GUI.Label(new Rect(18f, 9f, ((Rect)(ref adminWindow)).width - 36f, 28f), "Portal Limit Admin", adminTitleStyle);
		if (GUI.Button(new Rect(((Rect)(ref adminWindow)).width - 38f, 8f, 28f, 26f), "X", adminButtonStyle))
		{
			showAdminWindow = false;
			return;
		}
		GUILayout.BeginArea(new Rect(16f, 48f, ((Rect)(ref adminWindow)).width - 32f, ((Rect)(ref adminWindow)).height - 62f));
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(statusMessage, adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (isAdmin)
		{
			DrawAdminTabs();
			adminScroll = GUILayout.BeginScrollView(adminScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(Math.Max(220f, ((Rect)(ref adminWindow)).height - 145f)) });
			if (adminTab == 0)
			{
				DrawServerSettingsTab();
			}
			else if (adminTab == 1)
			{
				DrawPortalSettingsTab();
			}
			else
			{
				DrawPortalInformationTab();
			}
			GUILayout.EndScrollView();
			if (adminTab == 2)
			{
				DrawInformationFooter();
			}
			else
			{
				DrawAdminFooter();
			}
		}
		else
		{
			GUILayout.Label("You need to be on the Valheim admin list to edit this.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (GUILayout.Button("Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				showAdminWindow = false;
			}
		}
		GUILayout.EndVertical();
		GUILayout.EndArea();
		GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, 42f));
	}

	private void EnsureAdminStyles()
	{
		//IL_0029: 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_0071: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Expected O, but got Unknown
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Expected O, but got Unknown
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a6: Expected O, but got Unknown
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Expected O, but got Unknown
		//IL_0203: Unknown result type (might be due to invalid IL or missing references)
		//IL_0219: Unknown result type (might be due to invalid IL or missing references)
		//IL_0223: Expected O, but got Unknown
		//IL_025d: Unknown result type (might be due to invalid IL or missing references)
		//IL_026f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0279: Expected O, but got Unknown
		//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c6: Expected O, but got Unknown
		//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f1: Expected O, but got Unknown
		//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0306: Expected O, but got Unknown
		//IL_0326: Unknown result type (might be due to invalid IL or missing references)
		//IL_0364: Unknown result type (might be due to invalid IL or missing references)
		//IL_036e: Expected O, but got Unknown
		//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0412: Unknown result type (might be due to invalid IL or missing references)
		//IL_0435: Unknown result type (might be due to invalid IL or missing references)
		//IL_043f: Expected O, but got Unknown
		//IL_044a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0454: Expected O, but got Unknown
		//IL_045c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0466: Expected O, but got Unknown
		//IL_0491: Unknown result type (might be due to invalid IL or missing references)
		//IL_049b: Expected O, but got Unknown
		//IL_04bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ea: Expected O, but got Unknown
		//IL_0539: Unknown result type (might be due to invalid IL or missing references)
		//IL_0543: Expected O, but got Unknown
		//IL_0563: Unknown result type (might be due to invalid IL or missing references)
		//IL_0579: Unknown result type (might be due to invalid IL or missing references)
		//IL_059e: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a8: Expected O, but got Unknown
		//IL_05b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_05be: Expected O, but got Unknown
		//IL_05ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_05d4: Expected O, but got Unknown
		//IL_05f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_05ff: Expected O, but got Unknown
		//IL_060a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0614: Expected O, but got Unknown
		if (!adminStylesReady)
		{
			adminWindowTexture = MakeAdminTexture(new Color(0.03f, 0.026f, 0.022f, 0.98f));
			adminHeaderTexture = MakeAdminTexture(new Color(0.18f, 0.115f, 0.065f, 0.98f));
			adminSectionTexture = MakeAdminTexture(new Color(0.13f, 0.085f, 0.052f, 0.96f));
			adminInputTexture = MakeAdminBorderTexture(new Color(0.12f, 0.105f, 0.08f, 1f), new Color(1f, 0.78f, 0.26f, 1f));
			adminButtonTexture = MakeAdminTexture(new Color(0.25f, 0.24f, 0.21f, 0.98f));
			adminButtonHoverTexture = MakeAdminTexture(new Color(0.36f, 0.31f, 0.23f, 0.98f));
			adminTabTexture = MakeAdminTexture(new Color(0.16f, 0.14f, 0.12f, 0.98f));
			adminTabSelectedTexture = MakeAdminTexture(new Color(0.45f, 0.31f, 0.14f, 0.98f));
			adminWindowStyle = new GUIStyle(GUI.skin.box);
			adminWindowStyle.normal.background = adminWindowTexture;
			adminWindowStyle.border = new RectOffset(8, 8, 8, 8);
			adminWindowStyle.padding = new RectOffset(14, 14, 14, 14);
			adminTitleStyle = new GUIStyle(GUI.skin.label);
			adminTitleStyle.fontSize = 18;
			adminTitleStyle.fontStyle = (FontStyle)1;
			adminTitleStyle.alignment = (TextAnchor)4;
			adminTitleStyle.normal.textColor = new Color(0.95f, 0.83f, 0.55f, 1f);
			adminLabelStyle = new GUIStyle(GUI.skin.label);
			adminLabelStyle.fontSize = 14;
			adminLabelStyle.wordWrap = true;
			adminLabelStyle.normal.textColor = new Color(0.88f, 0.84f, 0.74f, 1f);
			adminHelpStyle = new GUIStyle(adminLabelStyle);
			adminHelpStyle.fontSize = 13;
			adminHelpStyle.normal.textColor = new Color(0.67f, 0.64f, 0.56f, 1f);
			adminSectionStyle = new GUIStyle(GUI.skin.box);
			adminSectionStyle.normal.background = adminSectionTexture;
			adminSectionStyle.border = new RectOffset(4, 4, 4, 4);
			adminSectionStyle.padding = new RectOffset(8, 8, 4, 4);
			adminSectionStyle.normal.textColor = new Color(0.98f, 0.82f, 0.43f, 1f);
			adminSectionStyle.alignment = (TextAnchor)3;
			adminSectionStyle.fontStyle = (FontStyle)1;
			adminSectionStyle.fontSize = 15;
			adminButtonStyle = new GUIStyle(GUI.skin.button);
			adminButtonStyle.normal.background = adminButtonTexture;
			adminButtonStyle.hover.background = adminButtonHoverTexture;
			adminButtonStyle.active.background = adminTabSelectedTexture;
			adminButtonStyle.normal.textColor = new Color(0.9f, 0.86f, 0.76f, 1f);
			adminButtonStyle.hover.textColor = new Color(1f, 0.9f, 0.58f, 1f);
			adminButtonStyle.active.textColor = Color.white;
			adminButtonStyle.fontSize = 13;
			adminButtonStyle.padding = new RectOffset(8, 8, 5, 5);
			adminButtonStyle.margin = new RectOffset(3, 3, 3, 3);
			adminTabStyle = new GUIStyle(adminButtonStyle);
			adminTabStyle.normal.background = adminTabTexture;
			adminTabStyle.fontStyle = (FontStyle)1;
			adminTabSelectedStyle = new GUIStyle(adminButtonStyle);
			adminTabSelectedStyle.normal.background = adminTabSelectedTexture;
			adminTabSelectedStyle.normal.textColor = Color.white;
			adminTabSelectedStyle.fontStyle = (FontStyle)1;
			adminTextFieldStyle = new GUIStyle(GUI.skin.textField);
			adminTextFieldStyle.normal.background = adminInputTexture;
			adminTextFieldStyle.focused.background = adminInputTexture;
			adminTextFieldStyle.hover.background = adminInputTexture;
			adminTextFieldStyle.border = new RectOffset(4, 4, 4, 4);
			adminTextFieldStyle.normal.textColor = new Color(1f, 0.98f, 0.86f, 1f);
			adminTextFieldStyle.focused.textColor = Color.white;
			adminTextFieldStyle.fontSize = 15;
			adminTextFieldStyle.padding = new RectOffset(12, 12, 8, 8);
			adminTextFieldStyle.margin = new RectOffset(3, 3, 5, 10);
			adminBoxStyle = new GUIStyle(GUI.skin.box);
			adminBoxStyle.normal.background = adminHeaderTexture;
			adminBoxStyle.padding = new RectOffset(6, 6, 6, 6);
			adminBoxStyle.margin = new RectOffset(0, 0, 2, 8);
			adminStylesReady = true;
		}
	}

	private static Texture2D MakeAdminTexture(Color color)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
		val.SetPixel(0, 0, color);
		val.Apply();
		return val;
	}

	private static Texture2D MakeAdminBorderTexture(Color fill, Color border)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Expected O, but got Unknown
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(16, 16, (TextureFormat)4, false);
		for (int i = 0; i < 16; i++)
		{
			for (int j = 0; j < 16; j++)
			{
				bool flag = j < 2 || i < 2 || j > 13 || i > 13;
				val.SetPixel(j, i, flag ? border : fill);
			}
		}
		val.Apply();
		return val;
	}

	private static void DestroyAdminTexture(Texture2D texture)
	{
		if ((Object)(object)texture != (Object)null)
		{
			Object.Destroy((Object)(object)texture);
		}
	}

	private void DrawAdminTabs()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Server Settings", (adminTab == 0) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 0;
			openDropdown = string.Empty;
		}
		if (GUILayout.Button("This Portal", (adminTab == 1) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 1;
			openDropdown = string.Empty;
		}
		if (GUILayout.Button("Portal Information", (adminTab == 2) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 2;
			openDropdown = string.Empty;
			RequestPortalInformation(showStatus: false);
		}
		GUILayout.EndHorizontal();
	}

	private void DrawSectionHeader(string text)
	{
		GUILayout.Box(text, adminSectionStyle, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.ExpandWidth(true),
			GUILayout.Height(30f)
		});
	}

	private void DrawServerSettingsTab()
	{
		GUILayout.Space(8f);
		DrawSectionHeader("Portal Limit");
		GUILayout.Label("How many counted portals may each non-admin player own? Use 0 for no limit.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		maxText = GUILayout.TextField(maxText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		adminBypass = DrawBoolDropdown("adminBypass", "Admin portal limit bypass", adminBypass, "Allowed", "Not allowed");
		enforceOnServer = DrawBoolDropdown("enforceOnServer", "Server removes extra portals over the limit", enforceOnServer, "Enabled", "Disabled");
		GUILayout.Label("Which portal types count toward the limit?", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Default covers normal wood and stone portals. Empty means every portal-like object.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		portalNamesText = GUILayout.TextField(portalNamesText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Your current portal count: " + serverPortalCount + " / " + maxPortalsPerPlayer, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(12f);
		DrawSectionHeader("Discovery Portals");
		GUILayout.Label("These settings apply to every locked discovery portal pair.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryEnabled = DrawBoolDropdown("discoveryEnabled", "Locked discovery portals", discoveryEnabled, "Enabled", "Disabled");
		discoveryAdminBypass = DrawBoolDropdown("discoveryAdminBypass", "Admin bypass for locked discovery portals", discoveryAdminBypass, "Allowed", "Not allowed");
		adminHoldEToEdit = DrawBoolDropdown("adminHoldEToEdit", "Admin hold E portal editor", adminHoldEToEdit, "Enabled", "Disabled");
		GUILayout.Label("When enabled, admins can hold E on a portal to edit that portal. Tap E still opens Valheim's normal Set Tag box.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		showDiscoveryHoverText = DrawBoolDropdown("showDiscoveryHoverText", "Lock/unlock hover text", showDiscoveryHoverText, "Shown", "Hidden");
		GUILayout.Space(12f);
		DrawSectionHeader("Default Player-Facing Hover Text");
		GUILayout.Label("These are the server defaults. A locked portal can override them on its own This Portal tab.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Locked entrance text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryLockedHoverTextField = GUILayout.TextField(discoveryLockedHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Unlocked entrance text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryRemoteHoverTextField = GUILayout.TextField(discoveryRemoteHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("After unlocked text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryUnlockedHoverTextField = GUILayout.TextField(discoveryUnlockedHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
	}

	private void DrawPortalSettingsTab()
	{
		GUILayout.Space(8f);
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			DrawSectionHeader("This Portal");
			GUILayout.Label("No portal was opened.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Look at a portal and hold E to open settings for that exact portal.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			return;
		}
		int discoveryRole = GetDiscoveryRole(val);
		string portalTagForUi = GetPortalTagForUi(val);
		GUILayout.Label("Portal tag", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Use the same tag on both portals, for example: Bog Witch.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (CanEditPortalTag(val))
		{
			discoveryLinkText = GUILayout.TextField(discoveryLinkText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			GUILayout.Label(discoveryLinkText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("This portal was built by another player. Admins can edit Portal Limit settings, but the portal tag stays protected.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUILayout.Space(8f);
		DrawSectionHeader("This portal");
		GUILayout.Label("Current: " + GetPortalRoleLabel(discoveryRole).ToUpperInvariant() + "  |  Tag: " + (string.IsNullOrWhiteSpace(portalTagForUi) ? "(NONE)" : portalTagForUi), adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		selectedPortalRole = DrawInlineChoice("selectedPortalRole", selectedPortalRole, PortalRoleChoiceLabels);
		int selectedPortalRoleValue = GetSelectedPortalRoleValue();
		if (selectedPortalRoleValue != 0)
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Relock Portal Tag");
			GUILayout.Label("For this portal tag, make every player discover the unlocked entrance again.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			bool flag2 = (GUI.enabled = !string.IsNullOrWhiteSpace(discoveryLinkText));
			if (GUILayout.Button("Relock this portal tag for players", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				RelockSelectedDiscoveryPortal();
			}
			GUI.enabled = true;
			if (!flag2)
			{
				GUILayout.Label("Enter a portal tag before relocking players.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
		switch (selectedPortalRoleValue)
		{
		case 1:
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Locked Entrance Options");
			selectedPortalShowMarker = DrawBoolInline("Unlock message marker", selectedPortalShowMarker, "SHOWN", "HIDDEN");
			selectedPortalSuppressGlow = !DrawBoolInline("Orange glow while locked", !selectedPortalSuppressGlow, "SHOWN", "HIDDEN");
			GUILayout.Space(8f);
			GUILayout.Label("Marker color", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			int num = selectedPortalColorIndex;
			selectedPortalColorIndex = DrawInlineChoice("selectedPortalColor", selectedPortalColorIndex, MarkerColorLabels);
			if (num != selectedPortalColorIndex)
			{
				selectedPortalColorText = MarkerColorValues[Mathf.Clamp(selectedPortalColorIndex, 0, MarkerColorValues.Length - 1)];
			}
			selectedPortalColorText = GUILayout.TextField(selectedPortalColorText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
			GUILayout.Label("Marker text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			int num2 = selectedPortalMarkerTextIndex;
			selectedPortalMarkerTextIndex = DrawInlineChoice("selectedPortalMarkerText", selectedPortalMarkerTextIndex, MarkerTextLabels);
			if (num2 != selectedPortalMarkerTextIndex)
			{
				selectedPortalMarkerText = MarkerTextLabels[Mathf.Clamp(selectedPortalMarkerTextIndex, 0, MarkerTextLabels.Length - 1)];
			}
			selectedPortalMarkerText = GUILayout.TextField(selectedPortalMarkerText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
			GUILayout.Label("Player-facing hover text for this portal", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedLockedHoverText = GUILayout.TextField(selectedLockedHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			break;
		}
		case 2:
			GUILayout.Space(10f);
			DrawSectionHeader("Unlocked Entrance Options");
			GUILayout.Label("Player-facing hover text for this portal", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedRemoteHoverText = GUILayout.TextField(selectedRemoteHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			break;
		}
		if (selectedPortalRoleValue != 0)
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Unlocked Message");
			GUILayout.Label("Text players see after they have unlocked this portal tag. Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedUnlockedHoverText = GUILayout.TextField(selectedUnlockedHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
	}

	private void DrawPortalInformationTab()
	{
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: 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_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(8f);
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			DrawSectionHeader("Portal Information");
			GUILayout.Label("No portal was opened.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Look at a portal and hold E to open settings for that exact portal.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			return;
		}
		if (GUILayout.Button("Refresh portal information", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			RequestPortalInformation(showStatus: true);
		}
		portalInfoShowThis = DrawCollapseHeader(portalInfoShowThis, "THIS PORTAL");
		if (portalInfoShowThis)
		{
			portalInfoThisScroll = GUILayout.BeginScrollView(portalInfoThisScroll, adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(220f) });
			GUILayout.TextArea(portalInfoThisText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(GetTextAreaHeight(portalInfoThisText, 170f)) });
			GUILayout.EndScrollView();
		}
		portalInfoShowWorld = DrawCollapseHeader(portalInfoShowWorld, "WORLD PORTALS");
		if (portalInfoShowWorld)
		{
			portalInfoWorldScroll = GUILayout.BeginScrollView(portalInfoWorldScroll, adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(260f) });
			GUILayout.TextArea(portalInfoWorldText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(GetTextAreaHeight(portalInfoWorldText, 220f)) });
			GUILayout.EndScrollView();
		}
	}

	private static float GetTextAreaHeight(string text, float minimum)
	{
		int num = 1;
		if (!string.IsNullOrEmpty(text))
		{
			num = text.Split(new char[1] { '\n' }).Length;
		}
		return Math.Max(minimum, 22f + (float)num * 20f);
	}

	private bool DrawCollapseHeader(bool expanded, string label)
	{
		GUILayout.Space(8f);
		if (GUILayout.Button((expanded ? "v " : "> ") + label, adminTabSelectedStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			expanded = !expanded;
		}
		return expanded;
	}

	private bool DrawBoolInline(string label, bool value, string trueLabel, string falseLabel)
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(label, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) });
		if (GUILayout.Button((value ? "> " : string.Empty) + trueLabel, value ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			value = true;
		}
		if (GUILayout.Button(((!value) ? "> " : string.Empty) + falseLabel, (!value) ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			value = false;
		}
		GUILayout.EndHorizontal();
		return value;
	}

	private int DrawInlineChoice(string id, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		for (int i = 0; i < labels.Length; i++)
		{
			if (GUILayout.Button(((i == selected) ? "> " : string.Empty) + labels[i], (i == selected) ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				selected = i;
				openDropdown = string.Empty;
			}
		}
		GUILayout.EndHorizontal();
		return selected;
	}

	private bool DrawBoolDropdown(string id, string label, bool value, string trueLabel, string falseLabel)
	{
		int selected = ((!value) ? 1 : 0);
		selected = DrawChoiceDropdown(id, label, selected, new string[2] { trueLabel, falseLabel });
		return selected == 0;
	}

	private int DrawChoiceDropdown(string id, int selected, string[] labels)
	{
		return DrawChoiceDropdown(id, string.Empty, selected, labels);
	}

	private int DrawChoiceDropdown(string id, string label, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (!string.IsNullOrWhiteSpace(label))
		{
			GUILayout.Label(label, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) });
		}
		string text = labels[selected] + "  v";
		if (GUILayout.Button(text, adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(190f) }))
		{
			openDropdown = ((openDropdown == id) ? string.Empty : id);
		}
		GUILayout.EndHorizontal();
		if (openDropdown == id)
		{
			GUILayout.BeginVertical(adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			for (int i = 0; i < labels.Length; i++)
			{
				string text2 = ((i == selected) ? "> " : "  ");
				if (GUILayout.Button(text2 + labels[i], adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
				{
					selected = i;
					openDropdown = string.Empty;
				}
			}
			GUILayout.EndVertical();
		}
		return selected;
	}

	private int DrawChoiceList(string id, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginVertical(adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		for (int i = 0; i < labels.Length; i++)
		{
			string text = ((i == selected) ? "> " : "  ");
			if (GUILayout.Button(text + labels[i], adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				selected = i;
				openDropdown = string.Empty;
			}
		}
		GUILayout.EndVertical();
		return selected;
	}

	private void DrawAdminFooter()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Apply", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			ApplyActiveTab(closeAfterApply: false);
		}
		if (GUILayout.Button("Apply & Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			ApplyActiveTab(closeAfterApply: true);
		}
		if (GUILayout.Button("Close Without Applying", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			showAdminWindow = false;
		}
		GUILayout.EndHorizontal();
	}

	private void DrawInformationFooter()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Refresh", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			RequestPortalInformation(showStatus: true);
		}
		if (GUILayout.Button("Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			showAdminWindow = false;
		}
		GUILayout.EndHorizontal();
	}

	private void ApplyActiveTab(bool closeAfterApply)
	{
		if (adminTab == 0)
		{
			SaveConfigFromUi();
		}
		else
		{
			MarkSelectedDiscoveryPortal(GetSelectedPortalRoleValue());
		}
		if (closeAfterApply)
		{
			showAdminWindow = false;
		}
	}

	private void ToggleAdminWindow()
	{
		if (!adminStatusKnown)
		{
			openAdminUiAfterPermissionCheck = true;
			RequestConfig(openUi: false);
			ShowMessage("Checking portal limit permissions...");
			return;
		}
		if (!isAdmin)
		{
			showAdminWindow = false;
			RequestCountForChat();
			return;
		}
		showAdminWindow = !showAdminWindow;
		openDropdown = string.Empty;
		if (showAdminWindow)
		{
			RequestConfig(openUi: true);
			RequestCount();
			CenterAdminWindowIfNeeded();
		}
	}

	private void OpenAdminWindowForPortal(TeleportWorld portal)
	{
		SelectPortalForEditing(portal, showStatus: false);
		showAdminWindow = true;
		openDropdown = string.Empty;
		adminTab = 1;
		statusMessage = "Portal selected. Choose what this portal should do, then Apply or Apply & Close.";
		CenterAdminWindowIfNeeded();
		RequestConfig(openUi: true);
		RequestCount();
	}

	private void SaveConfigFromUi()
	{
		if (!int.TryParse(maxText, out var result))
		{
			statusMessage = "Max portals must be a whole number.";
			return;
		}
		string text = "max=" + Math.Max(0, result) + "\nadminBypass=" + adminBypass + "\nenforceOnServer=" + enforceOnServer + "\nportalPrefabNames=" + Escape(portalNamesText) + "\ndiscoveryEnabled=" + discoveryEnabled + "\ndiscoveryAdminBypass=" + discoveryAdminBypass + "\nadminHoldEToEdit=" + adminHoldEToEdit + "\nshowDiscoveryHoverText=" + showDiscoveryHoverText + "\ndiscoveryLockedHoverText=" + Escape(discoveryLockedHoverTextField) + "\ndiscoveryRemoteHoverText=" + Escape(discoveryRemoteHoverTextField) + "\ndiscoveryUnlockedHoverText=" + Escape(discoveryUnlockedHoverTextField) + "\ndiscoveryLockedMessage=" + Escape(discoveryLockedMessageField) + "\ndiscoveryUnlockMessage=" + Escape(discoveryUnlockMessageField) + "\ndiscoveryShowWorldMarker=" + discoveryShowWorldMarker + "\ndiscoveryTintPortalModel=" + discoveryTintPortalModel + "\ndiscoverySuppressLockedGlow=" + discoverySuppressLockedGlow + "\ndiscoveryLockedColor=" + Escape(discoveryLockedColorField) + "\ndiscoveryRemoteColor=" + Escape(discoveryRemoteColorField) + "\ndiscoveryUnlockedColor=" + Escape(discoveryUnlockedColorField);
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.config_update", new object[1] { text });
		statusMessage = "Saving portal limit settings...";
	}

	private void RequestConfig(bool openUi)
	{
		configRequested = true;
		if (openUi)
		{
			statusMessage = "Loading portal limit settings...";
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.config_request", new object[1] { (openUi ? string.Empty : "[silent]") + GetPlayerName() });
		RequestUnlockList();
	}

	private void RequestCount()
	{
		long localPlayerId = GetLocalPlayerId();
		if (localPlayerId != 0)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.count_request", new object[1] { localPlayerId });
		}
	}

	private void RequestCountForChat()
	{
		showCountMessageOnNextResponse = true;
		RequestCount();
		ShowMessage("Checking portal count...");
	}

	private void RequestPlacementCountSoon()
	{
		((MonoBehaviour)this).StartCoroutine(RequestPlacementCountNextFrame());
	}

	private IEnumerator RequestPlacementCountNextFrame()
	{
		yield return (object)new WaitForSeconds(0.35f);
		long playerId = GetLocalPlayerId();
		int count = CountPortals(playerId);
		string maxText = ((maxPortalsPerPlayer > 0) ? maxPortalsPerPlayer.ToString() : "unlimited");
		serverPortalCount = count;
		ShowMessage("Portals placed: " + count + " / " + maxText);
		RequestCount();
	}

	private void RequestUnlockList()
	{
		long localPlayerId = GetLocalPlayerId();
		if (localPlayerId != 0)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_unlock_list_request", new object[1] { localPlayerId });
		}
	}

	private void RequestPortalInformation(bool showStatus)
	{
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			portalInfoThisText = "No portal was opened.";
			portalInfoWorldText = "Open this tab while editing a portal.";
			return;
		}
		ZNetView component = ((Component)val).GetComponent<ZNetView>();
		ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		portalInfoRequestedPortalKey = ((val2 != null) ? ((object)(ZDOID)(ref val2.m_uid)).ToString() : GetPortalKey(val));
		portalInfoThisText = "Loading authoritative portal information from the server...";
		string portalTagForUi = GetPortalTagForUi(val);
		if (ZRoutedRpc.instance == null)
		{
			portalInfoWorldText = "Portal Limit server RPC is not ready yet.";
			return;
		}
		if (showStatus)
		{
			statusMessage = "Loading portal information...";
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.admin_report_request", new object[1] { "portalinfo|" + Escape(portalTagForUi) + "|" + Escape(portalInfoRequestedPortalKey) });
	}

	private static void OnConfigResponse(long sender, string payload)
	{
		Instance.ApplyConfigPayload(payload);
	}

	private static void OnCountResponse(long sender, int count, int max, bool limitReached, string payload)
	{
		Instance.serverPortalCount = count;
		Instance.maxPortalsPerPlayer = max;
		Instance.ApplyConfigPayload(payload);
		if (Instance.showPlacementCountOnNextResponse)
		{
			Instance.showPlacementCountOnNextResponse = false;
			string text = ((max > 0) ? max.ToString() : "unlimited");
			Instance.ShowMessage("Portals placed: " + count + " / " + text);
		}
		else if (Instance.showCountMessageOnNextResponse)
		{
			Instance.showCountMessageOnNextResponse = false;
			string text = ((max > 0) ? max.ToString() : "unlimited");
			Instance.ShowMessage("Portals: " + count + " / " + text);
		}
	}

	private static void OnNotice(long sender, string message)
	{
		Instance.ShowMessage(message);
	}

	private static void OnDiscoveryUnlockListResponse(long sender, string payload)
	{
		Instance.ApplyUnlockPayload(payload);
	}

	private static void OnAdminReportResponse(long sender, string payload)
	{
		if (string.IsNullOrWhiteSpace(payload))
		{
			Terminal.Log((object)"Portal Limit: empty report.");
			return;
		}
		if ((Object)(object)Instance != (Object)null && payload.StartsWith("PortalInfoResponse\n", StringComparison.Ordinal))
		{
			Instance.ApplyPortalInformationPayload(payload);
			return;
		}
		string[] array = payload.Split(new char[1] { '\n' });
		foreach (string text in array)
		{
			Terminal.Log((object)text);
		}
		if ((Object)(object)Instance != (Object)null)
		{
			Instance.ShowMessage("Portal Limit report written to F5 console.");
		}
	}

	private void ApplyPortalInformationPayload(string payload)
	{
		string text = payload.Substring("PortalInfoResponse\n".Length);
		string[] array = text.Split(new string[1] { "\n---WORLD---\n" }, StringSplitOptions.None);
		if (array.Length > 0 && !string.IsNullOrWhiteSpace(array[0]))
		{
			portalInfoThisText = array[0].Trim();
		}
		portalInfoWorldText = ((array.Length > 1) ? array[1].Trim() : text.Trim());
		statusMessage = "Portal information loaded.";
	}

	private void ApplyConfigPayload(string payload)
	{
		Dictionary<string, string> dictionary = ParsePayload(payload);
		if (dictionary.TryGetValue("isAdmin", out var value) && bool.TryParse(value, out var result))
		{
			isAdmin = result;
			adminStatusKnown = true;
			if (!isAdmin)
			{
				showAdminWindow = false;
			}
			if (openAdminUiAfterPermissionCheck)
			{
				openAdminUiAfterPermissionCheck = false;
				if (isAdmin)
				{
					showAdminWindow = true;
					openDropdown = string.Empty;
					CenterAdminWindowIfNeeded();
					RequestConfig(openUi: true);
					RequestCount();
				}
				else
				{
					RequestCountForChat();
				}
			}
		}
		if (dictionary.TryGetValue("max", out value) && int.TryParse(value, out var result2))
		{
			maxPortalsPerPlayer = result2;
			maxText = result2.ToString();
		}
		if (dictionary.TryGetValue("adminBypass", out value) && bool.TryParse(value, out result))
		{
			adminBypass = result;
		}
		if (dictionary.TryGetValue("enforceOnServer", out value) && bool.TryParse(value, out result))
		{
			enforceOnServer = result;
		}
		if (dictionary.TryGetValue("portalPrefabNames", out value))
		{
			portalPrefabNames = value;
			portalNamesText = value;
		}
		if (dictionary.TryGetValue("discoveryEnabled", out value) && bool.TryParse(value, out result))
		{
			discoveryEnabled = result;
		}
		if (dictionary.TryGetValue("discoveryAdminBypass", out value) && bool.TryParse(value, out result))
		{
			discoveryAdminBypass = result;
		}
		if (dictionary.TryGetValue("adminHoldEToEdit", out value) && bool.TryParse(value, out result))
		{
			adminHoldEToEdit = result;
		}
		if (dictionary.TryGetValue("showDiscoveryHoverText", out value) && bool.TryParse(value, out result))
		{
			showDiscoveryHoverText = result;
		}
		if (dictionary.TryGetValue("discoveryLockedHoverText", out value))
		{
			discoveryLockedHoverText = value;
			discoveryLockedHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryRemoteHoverText", out value))
		{
			discoveryRemoteHoverText = value;
			discoveryRemoteHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockedHoverText", out value))
		{
			discoveryUnlockedHoverText = value;
			discoveryUnlockedHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryLockedMessage", out value))
		{
			discoveryLockedMessage = value;
			discoveryLockedMessageField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockMessage", out value))
		{
			discoveryUnlockMessage = value;
			discoveryUnlockMessageField = value;
		}
		if (dictionary.TryGetValue("discoveryShowWorldMarker", out value) && bool.TryParse(value, out result))
		{
			discoveryShowWorldMarker = result;
		}
		if (dictionary.TryGetValue("discoveryTintPortalModel", out value) && bool.TryParse(value, out result))
		{
			discoveryTintPortalModel = result;
		}
		if (dictionary.TryGetValue("discoverySuppressLockedGlow", out value) && bool.TryParse(value, out result))
		{
			discoverySuppressLockedGlow = result;
		}
		if (dictionary.TryGetValue("discoveryLockedColor", out value))
		{
			discoveryLockedColor = value;
			discoveryLockedColorField = value;
		}
		if (dictionary.TryGetValue("discoveryRemoteColor", out value))
		{
			discoveryRemoteColor = value;
			discoveryRemoteColorField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockedColor", out value))
		{
			discoveryUnlockedColor = value;
			discoveryUnlockedColorField = value;
		}
		if (dictionary.TryGetValue("message", out value) && !string.IsNullOrWhiteSpace(value))
		{
			statusMessage = value;
			ShowMessage(value);
		}
	}

	private void ApplyUnlockPayload(string payload)
	{
		Dictionary<string, string> dictionary = ParsePayload(payload);
		unlockedDiscoveryLinks.Clear();
		if (dictionary.TryGetValue("links", out var value))
		{
			string[] array = value.Split(new char[1] { ';' });
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (!string.IsNullOrWhiteSpace(text2))
				{
					unlockedDiscoveryLinks.Add(text2);
				}
			}
		}
		if (dictionary.TryGetValue("message", out var value2) && !string.IsNullOrWhiteSpace(value2))
		{
			ShowMessage(value2);
		}
	}

	private void MarkSelectedDiscoveryPortal(int role)
	{
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			statusMessage = "No portal selected. Look at a portal and click Select first.";
			return;
		}
		ZNetView component = ((Component)val).GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsValid())
		{
			statusMessage = "That portal has no valid network view.";
			return;
		}
		string text = discoveryLinkText.Trim();
		bool flag = CanEditPortalTag(val);
		if (!flag)
		{
			text = GetPortalTagForUi(val).Trim();
		}
		if (role != 0 && string.IsNullOrWhiteSpace(text))
		{
			statusMessage = "Enter a portal tag first.";
			return;
		}
		discoveryLinkText = text;
		if (flag)
		{
			SetPortalTag(val, text);
		}
		string text2 = ((role == 0) ? string.Empty : text);
		string normalized = string.Empty;
		string text3 = string.Empty;
		if (role != 0)
		{
			if (!TryNormalizeHexColor(selectedPortalColorText, out normalized))
			{
				statusMessage = "Enter a color like #ff3030.";
				return;
			}
			selectedPortalColorText = normalized;
			text3 = NormalizeMarkerTextValue(selectedPortalMarkerText);
		}
		string text4 = "role=" + role + "\nlink=" + Escape(text2) + "\nmarkerColor=" + Escape(normalized) + "\nmarkerText=" + Escape(text3) + "\nshowMarker=" + selectedPortalShowMarker + "\nsuppressGlow=" + selectedPortalSuppressGlow + "\nlockedHoverText=" + Escape(selectedLockedHoverText) + "\nremoteHoverText=" + Escape(selectedRemoteHoverText) + "\nunlockedHoverText=" + Escape(selectedUnlockedHoverText);
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_mark_request", new object[2]
		{
			component.GetZDO().m_uid,
			text4
		});
		ApplyLocalPortalMarker(val, role, text2, normalized, text3, selectedPortalShowMarker, selectedPortalSuppressGlow, selectedLockedHoverText, selectedRemoteHoverText, selectedUnlockedHoverText);
		statusMessage = "Portal settings saved.";
		Log.LogInfo((object)("Sent discovery portal marker request role=" + role + " tag='" + text + "' portal=" + component.GetZDO().m_uid));
	}

	private void RelockSelectedDiscoveryPortal()
	{
		RelockDiscoveryPortalTag(discoveryLinkText);
	}

	private void RelockDiscoveryPortalTag(string portalTagText)
	{
		string text = (portalTagText ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			statusMessage = "Enter a portal tag first.";
			return;
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_relock_request", new object[1] { text });
		unlockedDiscoveryLinks.Remove(text);
		statusMessage = "Relock request sent for portal tag '" + text + "'.";
	}

	private void ReadSelectedPortalSettings()
	{
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			statusMessage = "No portal selected. Look at a portal and click Select first.";
		}
		else
		{
			LoadPortalSettings(val, showStatus: true);
		}
	}

	private string BuildLocalPortalInformation(TeleportWorld portal)
	{
		if ((Object)(object)portal == (Object)null)
		{
			return "THIS PORTAL\nNo portal selected.";
		}
		ZNetView component = ((Component)portal).GetComponent<ZNetView>();
		ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		string text = ((val != null) ? val.GetString("PortalLimit_BuiltUtc", string.Empty) : string.Empty);
		string text2 = GetPortalCreatorName(portal);
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = "unknown";
		}
		string text3 = GetPortalTagForUi(portal);
		if (string.IsNullOrWhiteSpace(text3))
		{
			text3 = "(none)";
		}
		string text4 = (string.IsNullOrWhiteSpace(text) ? "unknown for older portals" : text);
		return "THIS PORTAL\nPortal Tag: " + text3 + "\nPlayer Who Built the Portal: " + text2 + "\nDate the portal was built: " + text4 + "\nPortal role: " + GetPortalRoleLabel(GetDiscoveryRole(portal)) + "\nPortal id: " + ((val != null) ? ((object)(ZDOID)(ref val.m_uid)).ToString() : "unknown");
	}

	private void SelectPortalForEditing(TeleportWorld portal, bool showStatus)
	{
		if ((Object)(object)portal == (Object)null)
		{
			if (showStatus)
			{
				statusMessage = "Look directly at a portal first.";
			}
		}
		else
		{
			selectedPortal = portal;
			LoadPortalSettings(portal, showStatus);
		}
	}

	private TeleportWorld GetSelectedPortal()
	{
		if ((Object)(object)selectedPortal != (Object)null)
		{
			ZNetView component = ((Component)selectedPortal).GetComponent<ZNetView>();
			if ((Object)(object)component != (Object)null && component.IsValid())
			{
				return selectedPortal;
			}
		}
		selectedPortal = null;
		loadedPortalKey = string.Empty;
		return null;
	}

	private void LoadPortalSettings(TeleportWorld portal, bool showStatus)
	{
		selectedPortalRole = GetDiscoveryRole(portal) switch
		{
			2 => 2, 
			1 => 1, 
			_ => 0, 
		};
		string discoveryLink = GetDiscoveryLink(portal);
		if (!string.IsNullOrWhiteSpace(discoveryLink))
		{
			discoveryLinkText = discoveryLink;
		}
		else
		{
			string portalTag = GetPortalTag(portal);
			if (!string.IsNullOrWhiteSpace(portalTag))
			{
				discoveryLinkText = portalTag;
			}
		}
		loadedPortalKey = GetPortalKey(portal);
		selectedPortalColorText = GetDiscoveryColor(portal);
		if (string.IsNullOrWhiteSpace(selectedPortalColorText))
		{
			selectedPortalColorText = MarkerColorValues[0];
		}
		selectedPortalColorIndex = GetMarkerColorIndex(selectedPortalColorText);
		selectedPortalMarkerText = GetDiscoveryMarkerText(portal);
		selectedPortalMarkerTextIndex = GetMarkerTextIndex(selectedPortalMarkerText);
		selectedPortalShowMarker = GetDiscoveryShowMarker(portal);
		selectedPortalSuppressGlow = GetDiscoverySuppressGlow(portal);
		selectedLockedHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryLockedHoverText");
		selectedRemoteHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryRemoteHoverText");
		selectedUnlockedHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryUnlockedHoverText");
		if (showStatus)
		{
			statusMessage = "Selected portal loaded. Choose what this portal does, then Apply.";
		}
	}

	private static void ApplyLocalPortalMarker(TeleportWorld portal, int role, string link, string markerColor, string markerText, bool showMarker, bool suppressGlow, string lockedHoverText, string remoteHoverText, string unlockedHoverText)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 != null)
		{
			val2.Set("PortalLimit_DiscoveryRole", role);
			val2.Set("PortalLimit_DiscoveryLink", (role == 0) ? string.Empty : link);
			val2.Set("PortalLimit_DiscoveryColor", (role == 0) ? string.Empty : markerColor);
			val2.Set("PortalLimit_DiscoveryMarkerText", (role == 0) ? string.Empty : NormalizeMarkerTextValue(markerText));
			val2.Set("PortalLimit_DiscoveryShowMarker", (role != 0) ? (showMarker ? 1 : 0) : 0);
			val2.Set("PortalLimit_DiscoverySuppressGlow", (role != 0) ? (suppressGlow ? 1 : 0) : 0);
			val2.Set("PortalLimit_DiscoveryLockedHoverText", (role == 0) ? string.Empty : (lockedHoverText ?? string.Empty).Trim());
			val2.Set("PortalLimit_DiscoveryRemoteHoverText", (role == 0) ? string.Empty : (remoteHoverText ?? string.Empty).Trim());
			val2.Set("PortalLimit_DiscoveryUnlockedHoverText", (role == 0) ? string.Empty : (unlockedHoverText ?? string.Empty).Trim());
		}
	}

	private int GetSelectedPortalRoleValue()
	{
		if (selectedPortalRole == 1)
		{
			return 1;
		}
		if (selectedPortalRole == 2)
		{
			return 2;
		}
		return 0;
	}

	private static string GetPortalRoleLabel(int role)
	{
		return role switch
		{
			1 => "Locked entrance", 
			2 => "Unlocked entrance", 
			_ => "Normal portal", 
		};
	}

	private static TeleportWorld GetHoveredPortal()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			return null;
		}
		GameObject hoverObject = ((Humanoid)Player.m_localPlayer).GetHoverObject();
		if ((Object)(object)hoverObject == (Object)null)
		{
			return null;
		}
		TeleportWorld val = hoverObject.GetComponent<TeleportWorld>();
		if ((Object)(object)val == (Object)null)
		{
			val = hoverObject.GetComponentInParent<TeleportWorld>();
		}
		return val;
	}

	private static string GetPortalKey(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? ((object)(ZDOID)(ref val2.m_uid)).ToString() : string.Empty;
	}

	private static string GetPortalCreatorName(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 == null)
		{
			return string.Empty;
		}
		string @string = val2.GetString(ZDOVars.s_creatorName, string.Empty);
		return @string ?? string.Empty;
	}

	private static bool CanEditPortalTag(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || (Object)(object)portal == (Object)null)
		{
			return false;
		}
		Piece val = ((Component)portal).GetComponent<Piece>();
		if ((Object)(object)val == (Object)null)
		{
			val = ((Component)portal).GetComponentInParent<Piece>();
		}
		if ((Object)(object)val == (Object)null)
		{
			return true;
		}
		long creator = val.GetCreator();
		long localPlayerId = GetLocalPlayerId();
		return creator == 0 || creator == localPlayerId;
	}

	private static string GetPortalTag(TeleportWorld portal)
	{
		MethodInfo methodInfo = AccessTools.Method(typeof(TeleportWorld), "GetText", (Type[])null, (Type[])null);
		if ((Object)(object)portal != (Object)null && methodInfo != null)
		{
			object obj = methodInfo.Invoke(portal, null);
			if (obj != null)
			{
				return obj.ToString();
			}
		}
		return string.Empty;
	}

	private static string GetPortalTagForUi(TeleportWorld portal)
	{
		string discoveryLink = GetDiscoveryLink(portal);
		if (!string.IsNullOrWhiteSpace(discoveryLink))
		{
			return discoveryLink;
		}
		return GetPortalTag(portal);
	}

	private static void SetPortalTag(TeleportWorld portal, string tag)
	{
		if ((Object)(object)portal == (Object)null)
		{
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(TeleportWorld), "SetText", new Type[1] { typeof(string) }, (Type[])null);
		if (methodInfo != null)
		{
			methodInfo.Invoke(portal, new object[1] { tag ?? string.Empty });
			return;
		}
		ZNetView component = ((Component)portal).GetComponent<ZNetView>();
		ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		if (val != null)
		{
			val.Set("tag", tag ?? string.Empty);
		}
	}

	private static void SetTargetFound(TeleportWorld portal, bool value)
	{
		if ((Object)(object)portal != (Object)null && TeleportWorldTargetFoundField != null)
		{
			TeleportWorldTargetFoundField.SetValue(portal, value);
		}
	}

	private static int GetDiscoveryRole(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetInt("PortalLimit_DiscoveryRole", 0) : 0;
	}

	private static string GetDiscoveryLink(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString("PortalLimit_DiscoveryLink", string.Empty) : string.Empty;
	}

	private static string GetDiscoveryColor(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString("PortalLimit_DiscoveryColor", string.Empty) : string.Empty;
	}

	private static string GetDiscoveryMarkerText(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? NormalizeMarkerTextValue(val2.GetString("PortalLimit_DiscoveryMarkerText", string.Empty)) : MarkerTextValues[0];
	}

	private bool GetDiscoveryShowMarker(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? (val2.GetInt("PortalLimit_DiscoveryShowMarker", discoveryShowWorldMarker ? 1 : 0) != 0) : discoveryShowWorldMarker;
	}

	private bool GetDiscoverySuppressGlow(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? (val2.GetInt("PortalLimit_DiscoverySuppressGlow", discoverySuppressLockedGlow ? 1 : 0) != 0) : discoverySuppressLockedGlow;
	}

	private static string GetDiscoveryHoverOverride(TeleportWorld portal, string key)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString(key, string.Empty) : string.Empty;
	}

	private Color GetDiscoveryVisualColor(TeleportWorld portal)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: 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_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: 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_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		string discoveryColor = GetDiscoveryColor(portal);
		if (!string.IsNullOrWhiteSpace(discoveryColor))
		{
			return ParseHexColor(discoveryColor, LockedOrange);
		}
		int discoveryRole = GetDiscoveryRole(portal);
		string discoveryLink = GetDiscoveryLink(portal);
		if (discoveryRole != 0 && !string.IsNullOrWhiteSpace(discoveryLink) && IsDiscoveryUnlocked(discoveryLink))
		{
			return ParseHexColor(discoveryUnlockedColor, Color.green);
		}
		if (discoveryRole == 2)
		{
			return ParseHexColor(discoveryRemoteColor, Color.cyan);
		}
		return ParseHexColor(discoveryLockedColor, LockedOrange);
	}

	private bool ShouldShowDiscoveryWorldMarker(TeleportWorld portal)
	{
		return discoveryEnabled && GetDiscoveryShowMarker(portal) && ShouldShowLockedEntranceX(portal);
	}

	private string GetDiscoveryMarkerDisplayText(TeleportWorld portal)
	{
		string discoveryMarkerText = GetDiscoveryMarkerText(portal);
		if (string.Equals(discoveryMarkerText, "Locked", StringComparison.OrdinalIgnoreCase))
		{
			return "LOCKED";
		}
		if (string.Equals(discoveryMarkerText, "X", StringComparison.OrdinalIgnoreCase))
		{
			return "X";
		}
		if (string.Equals(discoveryMarkerText, "UnlockOnOtherSide", StringComparison.OrdinalIgnoreCase))
		{
			return "UNLOCK ON OTHER SIDE";
		}
		return string.IsNullOrWhiteSpace(discoveryMarkerText) ? "UNLOCK ON OTHER SIDE" : discoveryMarkerText;
	}

	private bool ShouldShowLockedEntranceX(TeleportWorld portal)
	{
		if (GetDiscoveryRole(portal) != 1)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		return !string.IsNullOrWhiteSpace(discoveryLink) && !IsDiscoveryUnlocked(discoveryLink);
	}

	private bool ShouldTintDiscoveryPortal(TeleportWorld portal)
	{
		return false;
	}

	private bool ShouldSuppressLockedPortalGlow(TeleportWorld portal)
	{
		if (!discoveryEnabled || !GetDiscoverySuppressGlow(portal) || GetDiscoveryRole(portal) != 1)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		return !string.IsNullOrWhiteSpace(discoveryLink) && !IsDiscoveryUnlocked(discoveryLink);
	}

	private static Color ParseHexColor(string text, Color fallback)
	{
		//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_00c4: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrWhiteSpace(text))
		{
			return fallback;
		}
		string text2 = text.Trim();
		if (text2.StartsWith("#", StringComparison.Ordinal))
		{
			text2 = text2.Substring(1);
		}
		if (text2.Length == 6 && int.TryParse(text2.Substring(0, 2), NumberStyles.HexNumber, null, out var result) && int.TryParse(text2.Substring(2, 2), NumberStyles.HexNumber, null, out var result2) && int.TryParse(text2.Substring(4, 2), NumberStyles.HexNumber, null, out var result3))
		{
			return new Color((float)result / 255f, (float)result2 / 255f, (float)result3 / 255f, 1f);
		}
		return fallback;
	}

	private static string NormalizeHexColor(string text, string fallback)
	{
		string text2 = (text ?? string.Empty).Trim();
		if (!text2.StartsWith("#", StringComparison.Ordinal))
		{
			text2 = "#" + text2;
		}
		Color val = default(Color);
		return ColorUtility.TryParseHtmlString(text2, ref val) ? text2 : fallback;
	}

	private static bool TryNormalizeHexColor(string text, out string normalized)
	{
		normalized = (text ?? string.Empty).Trim();
		if (!normalized.StartsWith("#", StringComparison.Ordinal))
		{
			normalized = "#" + normalized;
		}
		Color val = default(Color);
		if (normalized.Length == 7 && ColorUtility.TryParseHtmlString(normalized, ref val))
		{
			return true;
		}
		normalized = string.Empty;
		return false;
	}

	private static int GetMarkerColorIndex(string color)
	{
		string a = NormalizeHexColor(color, MarkerColorValues[0]);
		for (int i = 0; i < MarkerColorValues.Length; i++)
		{
			if (string.Equals(a, MarkerColorValues[i], StringComparison.OrdinalIgnoreCase))
			{
				return i;
			}
		}
		return 0;
	}

	private static string NormalizeMarkerTextValue(string value)
	{
		string text = (value ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			return MarkerTextLabels[0];
		}
		for (int i = 0; i < MarkerTextValues.Length; i++)
		{
			if (string.Equals(text, MarkerTextValues[i], StringComparison.OrdinalIgnoreCase) || string.Equals(text, MarkerTextLabels[i], StringComparison.OrdinalIgnoreCase))
			{
				return MarkerTextLabels[i];
			}
		}
		return text;
	}

	private static int GetMarkerTextIndex(string value)
	{
		string a = NormalizeMarkerTextValue(value);
		for (int i = 0; i < MarkerTextValues.Length; i++)
		{
			if (string.Equals(a, MarkerTextValues[i], StringComparison.OrdinalIgnoreCase) || string.Equals(a, MarkerTextLabels[i], StringComparison.OrdinalIgnoreCase))
			{
				return i;
			}
		}
		return 0;
	}

	private bool IsDiscoveryUnlocked(string link)
	{
		if (string.IsNullOrWhiteSpace(link))
		{
			return false;
		}
		return unlockedDiscoveryLinks.Contains(link);
	}

	private void UnlockDiscoveryLink(string link, TeleportWorld portal)
	{
		//IL_005a: 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_0060: 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_0099: 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)
		if (!string.IsNullOrWhiteSpace(link))
		{
			unlockedDiscoveryLinks.Add(link);
			long localPlayerId = GetLocalPlayerId();
			if (localPlayerId != 0 && ZRoutedRpc.instance != null)
			{
				Vector3 val = (((Object)(object)portal != (Object)null) ? ((Component)portal).transform.position : Vector3.zero);
				ZNetView val2 = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
				ZDOID val3 = (((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO().m_uid : ZDOID.None);
				string text = "link=" + Escape(link.Trim()) + "\nplayerName=" + Escape(GetPlayerName()) + "\nportalId=" + Escape(((object)(ZDOID)(ref val3)).ToString()) + "\nx=" + val.x.ToString("F1") + "\ny=" + val.y.ToString("F1") + "\nz=" + val.z.ToString("F1");
				ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_unlock_request", new object[2] { localPlayerId, text });
			}
		}
	}

	private static bool ShouldBlockPortalUse(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || !Instance.discoveryEnabled || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return false;
		}
		int discoveryRole = GetDiscoveryRole(portal);
		if (discoveryRole == 0)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		if (string.IsNullOrWhiteSpace(discoveryLink))
		{
			return false;
		}
		if (Instance.discoveryAdminBypass && Instance.isAdmin)
		{
			return false;
		}
		switch (discoveryRole)
		{
		case 2:
			if (!Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				Instance.UnlockDiscoveryLink(discoveryLink, portal);
				Instance.ShowMessage(Instance.discoveryUnlockMessage);
			}
			return false;
		case 1:
			if (!Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				Instance.ShowMessage(Instance.discoveryLockedMessage);
				Instance.RequestUnlockList();
				return true;
			}
			break;
		}
		return false;
	}

	private static bool ShouldBlockBlankPortalUse(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || (Object)(object)portal == (Object)null)
		{
			return false;
		}
		if (string.IsNullOrWhiteSpace(GetPortalTag(portal)))
		{
			Instance.ShowMessage("Set a portal tag first.");
			return true;
		}
		return false;
	}

	internal static bool WouldExceedPortalLimit(Piece piece)
	{
		PortalLimitClientPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)piece == (Object)null || !instance.IsLimitedPortal(piece))
		{
			return false;
		}
		if (instance.maxPortalsPerPlayer <= 0)
		{
			return false;
		}
		if (instance.adminBypass && instance.isAdmin)
		{
			return false;
		}
		long localPlayerId = GetLocalPlayerId();
		int num = instance.CountPortals(localPlayerId);
		return num >= instance.maxPortalsPerPlayer;
	}

	private bool IsLimitedPortal(Piece piece)
	{
		if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponent<TeleportWorld>() == (Object)null)
		{
			return false;
		}
		HashSet<string> portalPrefabNameSet = GetPortalPrefabNameSet();
		if (portalPrefabNameSet.Count == 0)
		{
			return true;
		}
		ZNetView component = ((Component)piece).GetComponent<ZNetView>();
		string prefabName = GetPrefabName(component, piece);
		return portalPrefabNameSet.Contains(prefabName);
	}

	private void ReportPortalMetadata(Piece piece, string action)
	{
		//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)
		if (!((Object)(object)piece == (Object)null) && IsLimitedPortal(piece) && ZRoutedRpc.instance != null)
		{
			ZNetView component = ((Component)piece).GetComponent<ZNetView>();
			ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
			if (val != null)
			{
				Vector3 position = ((Component)piece).transform.position;
				string text = "action=" + Escape(action) + "\nportalId=" + Escape(((object)(ZDOID)(ref val.m_uid)).ToString()) + "\nplayerId=" + GetLocalPlayerId() + "\nplayerName=" + Escape(GetPlayerName()) + "\nx=" + position.x.ToString("F1") + "\ny=" + position.y.ToString("F1") + "\nz=" + position.z.ToString("F1");
				ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.portal_metadata_update", new object[1] { text });
			}
		}
	}

	private void ReportPortalMetadata(TeleportWorld portal, string action)
	{
		if (!((Object)(object)portal == (Object)null))
		{
			Piece val = ((Component)portal).GetComponent<Piece>();
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)portal).GetComponentInParent<Piece>();
			}
			ReportPortalMetadata(val, action);
		}
	}

	private static string GetPrefabName(ZNetView nview, Piece piece)
	{
		if ((Object)(object)nview != (Object)null && GetPrefabNameMethod != null)
		{
			object obj = GetPrefabNameMethod.Invoke(nview, null);
			if (obj != null)
			{
				return obj.ToString();
			}
		}
		return ((Object)(object)piece != (Object)null) ? ((Object)piece).name.Replace("(Clone)", string.Empty) : string.Empty;
	}

	private int CountPortals(long creatorId)
	{
		if (creatorId == 0 || AllPiecesField == null)
		{
			return serverPortalCount;
		}
		int num = 0;
		if (!(AllPiecesField.GetValue(null) is IEnumerable enumerable))
		{
			return serverPortalCount;
		}
		foreach (object item in enumerable)
		{
			Piece val = (Piece)((item is Piece) ? item : null);
			if ((Object)(object)val != (Object)null && val.GetCreator() == creatorId && IsLimitedPortal(val))
			{
				num++;
			}
		}
		return num;
	}

	private HashSet<string> GetPortalPrefabNameSet()
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		string[] array = portalPrefabNames.Split(new char[1] { ',' });
		foreach (string text in array)
		{
			string text2 = text.Trim();
			if (!string.IsNullOrWhiteSpace(text2))
			{
				hashSet.Add(text2);
			}
		}
		return hashSet;
	}

	private static long GetLocalPlayerId()
	{
		try
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null && GetPlayerIdMethod != null)
			{
				object obj = GetPlayerIdMethod.Invoke(Player.m_localPlayer, null);
				if (obj != null && obj.GetType() == typeof(long))
				{
					return (long)obj;
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not read local player ID: " + ex.Message));
		}
		return 0L;
	}

	private static string GetPlayerName()
	{
		return ((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "unknown-player";
	}

	internal void ShowMessage(string message)
	{
		if ((Object)(object)MessageHud.instance != (Object)null)
		{
			MessageHud.instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false);
		}
		else
		{
			Log.LogMessage((object)message);
		}
	}

	private static Dictionary<string, string> ParsePayload(string payload)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		if (string.IsNullOrEmpty(payload))
		{
			return dictionary;
		}
		string[] array = payload.Split(new char[1] { '\n' });
		foreach (string text in array)
		{
			int num = text.IndexOf('=');
			if (num > 0)
			{
				dictionary[text.Substring(0, num)] = Unescape(text.Substring(num + 1));
			}
		}
		return dictionary;
	}

	private static string Escape(string value)
	{
		return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\n", "\\n").Replace("=", "\\e");
	}

	private static string Unescape(string value)
	{
		return (value ?? string.Empty).Replace("\\e", "=").Replace("\\n", "\n").Replace("\\\\", "\\");
	}
}

BepInEx\plugins\PortalLimitClient.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace PortalLimit.Client;

[BepInPlugin("keith.valheim.portallimit.client", "Portal Limit and Discovery Client", "0.1.4")]
public class PortalLimitClientPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Chat), "InputText")]
	private static class ChatInputPatch
	{
		private static bool Prefix(Chat __instance)
		{
			string a = (((Object)(object)((Terminal)__instance).m_input != (Object)null) ? ((TMP_InputField)((Terminal)__instance).m_input).text : string.Empty);
			if (string.Equals(a, "/portals", StringComparison.OrdinalIgnoreCase))
			{
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.RequestCountForChat();
				}
				if ((Object)(object)((Terminal)__instance).m_input != (Object)null)
				{
					((TMP_InputField)((Terminal)__instance).m_input).text = string.Empty;
				}
				__instance.Hide();
				return false;
			}
			if (!string.Equals(a, "/portallimit", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if ((Object)(object)Instance != (Object)null)
			{
				Instance.ToggleAdminWindow();
			}
			if ((Object)(object)((Terminal)__instance).m_input != (Object)null)
			{
				((TMP_InputField)((Terminal)__instance).m_input).text = string.Empty;
			}
			__instance.Hide();
			return false;
		}
	}

	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	private static class PlacePiecePatch
	{
		private static bool Prefix(Piece piece, ref bool __result)
		{
			if (!WouldExceedPortalLimit(piece))
			{
				return true;
			}
			__result = false;
			Instance.ShowMessage("Portal limit reached: " + Instance.maxPortalsPerPlayer + " portal(s) per player.");
			Instance.RequestCount();
			return false;
		}

		private static void Postfix(Piece piece, bool __result)
		{
			if (__result && (Object)(object)Instance != (Object)null && (Object)(object)piece != (Object)null && Instance.IsLimitedPortal(piece))
			{
				Instance.ReportPortalMetadata(piece, "placed");
				Instance.RequestPlacementCountSoon();
			}
		}
	}

	[HarmonyPatch(typeof(Piece), "CanBeRemoved")]
	private static class PieceCanBeRemovedPatch
	{
		private static void Postfix(Piece __instance, ref bool __result)
		{
			if (__result && !((Object)(object)Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && Instance.IsLimitedPortal(__instance))
			{
				long creator = __instance.GetCreator();
				long localPlayerId = GetLocalPlayerId();
				if (!Instance.isAdmin && creator != 0 && creator != localPlayerId)
				{
					__result = false;
					Instance.ShowMessage("Only the builder or an admin can remove this portal.");
				}
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Interact")]
	private static class TeleportWorldInteractPatch
	{
		private static bool Prefix(TeleportWorld __instance, Humanoid human, bool hold, ref bool __result)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)human != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (Instance.replayingVanillaPortalInteract)
			{
				return true;
			}
			if (Instance.isAdmin && Instance.adminHoldEToEdit && !hold)
			{
				Instance.BeginDelayedPortalInteract(__instance);
				__result = false;
				return false;
			}
			if (hold && Instance.isAdmin && Instance.adminHoldEToEdit)
			{
				Instance.OpenAdminWindowForPortal(__instance);
				Instance.ResetHoldInteract();
				__result = false;
				return false;
			}
			if (!hold && ShouldBlockPortalUse(__instance))
			{
				__result = false;
				return false;
			}
			return true;
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Teleport")]
	private static class TeleportWorldTeleportPatch
	{
		private static bool Prefix(TeleportWorld __instance, Player player)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			bool flag = !ShouldBlockBlankPortalUse(__instance) && !ShouldBlockPortalUse(__instance);
			if (flag && (Object)(object)Instance != (Object)null)
			{
				Instance.ReportPortalMetadata(__instance, "used");
			}
			return flag;
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "GetHoverText")]
	private static class TeleportWorldHoverTextPatch
	{
		private static void Postfix(TeleportWorld __instance, ref string __result)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return;
			}
			if (string.IsNullOrWhiteSpace(GetPortalTag(__instance)) && !string.IsNullOrEmpty(__result))
			{
				__result = __result.Replace(" [CONNECTED]", string.Empty).Replace("[CONNECTED]", string.Empty);
			}
			if (Instance.isAdmin)
			{
				if (Instance.adminHoldEToEdit)
				{
					__result += "\n<color=yellow>Hold E: Portal Limit settings</color>";
				}
				string portalCreatorName = GetPortalCreatorName(__instance);
				if (!string.IsNullOrWhiteSpace(portalCreatorName))
				{
					__result = __result + "\n<color=#ffd27a>Built by: " + portalCreatorName + "</color>";
				}
			}
			if (!Instance.discoveryEnabled || (!Instance.showDiscoveryHoverText && !Instance.isAdmin))
			{
				return;
			}
			int discoveryRole = GetDiscoveryRole(__instance);
			if (discoveryRole == 0)
			{
				return;
			}
			string discoveryLink = GetDiscoveryLink(__instance);
			if (string.IsNullOrWhiteSpace(discoveryLink))
			{
				return;
			}
			string text = string.Empty;
			if (Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryUnlockedHoverText");
				if (string.IsNullOrWhiteSpace(text))
				{
					text = Instance.discoveryUnlockedHoverText;
				}
			}
			else
			{
				switch (discoveryRole)
				{
				case 1:
					text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryLockedHoverText");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = Instance.discoveryLockedHoverText;
					}
					break;
				case 2:
					text = GetDiscoveryHoverOverride(__instance, "PortalLimit_DiscoveryRemoteHoverText");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = Instance.discoveryRemoteHoverText;
					}
					break;
				}
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				__result = __result + "\n<color=orange>" + text + "</color>";
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "TargetFound")]
	private static class TeleportWorldTargetFoundPatch
	{
		private static void Postfix(TeleportWorld __instance, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				__result = false;
				SetTargetFound(__instance, value: false);
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "HaveTarget")]
	private static class TeleportWorldHaveTargetPatch
	{
		private static void Postfix(TeleportWorld __instance, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				__result = false;
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Update")]
	private static class TeleportWorldVisualPatch
	{
		private static void Postfix(TeleportWorld __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				PortalLimitDiscoveryVisual portalLimitDiscoveryVisual = ((Component)__instance).GetComponent<PortalLimitDiscoveryVisual>();
				if ((Object)(object)portalLimitDiscoveryVisual == (Object)null)
				{
					portalLimitDiscoveryVisual = ((Component)__instance).gameObject.AddComponent<PortalLimitDiscoveryVisual>();
				}
				portalLimitDiscoveryVisual.Refresh(__instance);
			}
		}
	}

	private class PortalLimitDiscoveryVisual : MonoBehaviour
	{
		private GameObject markerRoot;

		private TextMesh markerText;

		private Renderer[] portalRenderers;

		private ParticleSystem[] portalParticles;

		private Light[] portalLights;

		private readonly Dictionary<Renderer, Color> originalColors = new Dictionary<Renderer, Color>();

		private readonly Dictionary<ParticleSystem, bool> originalParticleStates = new Dictionary<ParticleSystem, bool>();

		private readonly Dictionary<Light, bool> originalLightStates = new Dictionary<Light, bool>();

		internal void Refresh(TeleportWorld portal)
		{
			//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_009c: 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)
			if (!((Object)(object)Instance == (Object)null) && !((Object)(object)portal == (Object)null))
			{
				bool flag = Instance.ShouldShowDiscoveryWorldMarker(portal);
				bool enabled = Instance.ShouldTintDiscoveryPortal(portal);
				bool suppress = Instance.ShouldSuppressLockedPortalGlow(portal);
				Color discoveryVisualColor = Instance.GetDiscoveryVisualColor(portal);
				EnsureMarker();
				markerRoot.SetActive(flag);
				if (flag)
				{
					SetMarkerWorldPose(portal);
					SetMarkerText(Instance.GetDiscoveryMarkerDisplayText(portal));
					SetMarkerColor(discoveryVisualColor);
				}
				UpdatePortalTint(enabled, discoveryVisualColor);
				UpdatePortalEffects(suppress);
			}
		}

		private void EnsureMarker()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: 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)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)markerRoot != (Object)null))
			{
				markerRoot = new GameObject("PortalLimit_DiscoveryMarker");
				markerRoot.transform.SetParent(((Component)this).transform, false);
				markerRoot.transform.localPosition = new Vector3(0f, 1.55f, 0f);
				markerRoot.transform.localRotation = Quaternion.identity;
				markerRoot.transform.localScale = Vector3.one;
				markerText = markerRoot.AddComponent<TextMesh>();
				markerText.text = "UNLOCK ON OTHER SIDE";
				markerText.anchor = (TextAnchor)4;
				markerText.alignment = (TextAlignment)1;
				markerText.fontSize = 96;
				markerText.characterSize = 0.01f;
				markerText.richText = false;
				SetMarkerColor(LockedOrange);
				markerRoot.SetActive(false);
			}
		}

		private void SetMarkerText(string text)
		{
			if (!((Object)(object)markerText == (Object)null))
			{
				markerText.text = text;
				if (string.Equals(text, "X", StringComparison.Ordinal))
				{
					markerText.fontSize = 128;
					markerText.characterSize = 0.03f;
				}
				else if (string.Equals(text, "LOCKED", StringComparison.Ordinal))
				{
					markerText.fontSize = 96;
					markerText.characterSize = 0.018f;
				}
				else
				{
					markerText.fontSize = 96;
					markerText.characterSize = 0.01f;
				}
			}
		}

		private void SetMarkerColor(Color color)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)markerText != (Object)null)
			{
				markerText.color = color;
			}
		}

		private void SetMarkerWorldPose(TeleportWorld portal)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: 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_0069: 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_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)markerRoot == (Object)null || (Object)(object)portal == (Object)null)
			{
				return;
			}
			Vector3 val = ((Component)portal).transform.position + Vector3.up * 1.55f;
			if ((Object)(object)Camera.main != (Object)null)
			{
				Vector3 val2 = ((Component)Camera.main).transform.position - val;
				if (((Vector3)(ref val2)).sqrMagnitude > 0.001f)
				{
					markerRoot.transform.position = val + ((Vector3)(ref val2)).normalized * 0.45f;
					markerRoot.transform.rotation = Quaternion.LookRotation(-((Vector3)(ref val2)).normalized, Vector3.up);
					return;
				}
			}
			markerRoot.transform.position = val;
			markerRoot.transform.rotation = ((Component)portal).transform.rotation;
		}

		private void UpdatePortalTint(bool enabled, Color color)
		{
			//IL_009b: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			if (portalRenderers == null)
			{
				portalRenderers = ((Component)this).GetComponentsInChildren<Renderer>(true);
			}
			Renderer[] array = portalRenderers;
			foreach (Renderer val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalColors.ContainsKey(val))
					{
						originalColors[val] = (val.material.HasProperty("_Color") ? val.material.color : Color.white);
					}
					if (val.material.HasProperty("_Color"))
					{
						val.material.color = (enabled ? Color.Lerp(originalColors[val], color, 0.45f) : originalColors[val]);
					}
				}
			}
		}

		private void UpdatePortalEffects(bool suppress)
		{
			ParticleSystem[] array;
			if (portalParticles == null)
			{
				portalParticles = ((Component)this).GetComponentsInChildren<ParticleSystem>(true);
				array = portalParticles;
				foreach (ParticleSystem val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						originalParticleStates[val] = ((Component)val).gameObject.activeSelf;
					}
				}
			}
			Light[] array2;
			if (portalLights == null)
			{
				portalLights = ((Component)this).GetComponentsInChildren<Light>(true);
				array2 = portalLights;
				foreach (Light val2 in array2)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						originalLightStates[val2] = ((Behaviour)val2).enabled;
					}
				}
			}
			array = portalParticles;
			foreach (ParticleSystem val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalParticleStates.TryGetValue(val, out var value))
					{
						value = true;
					}
					((Component)val).gameObject.SetActive(!suppress && value);
					if (!suppress && value && !val.isPlaying)
					{
						val.Play(true);
					}
				}
			}
			array2 = portalLights;
			foreach (Light val2 in array2)
			{
				if (!((Object)(object)val2 == (Object)null) && !((Component)val2).transform.IsChildOf(markerRoot.transform))
				{
					if (!originalLightStates.TryGetValue(val2, out var value2))
					{
						value2 = true;
					}
					((Behaviour)val2).enabled = !suppress && value2;
				}
			}
		}
	}

	public const string PluginGuid = "keith.valheim.portallimit.client";

	public const string PluginName = "Portal Limit and Discovery Client";

	public const string PluginVersion = "0.1.4";

	private const string RpcConfigRequest = "keith.valheim.portallimit.config_request";

	private const string RpcConfigResponse = "keith.valheim.portallimit.config_response";

	private const string RpcConfigUpdate = "keith.valheim.portallimit.config_update";

	private const string RpcCountRequest = "keith.valheim.portallimit.count_request";

	private const string RpcCountResponse = "keith.valheim.portallimit.count_response";

	private const string RpcNotice = "keith.valheim.portallimit.notice";

	private const string RpcDiscoveryMarkRequest = "keith.valheim.portallimit.discovery_mark_request";

	private const string RpcDiscoveryUnlockRequest = "keith.valheim.portallimit.discovery_unlock_request";

	private const string RpcDiscoveryRelockRequest = "keith.valheim.portallimit.discovery_relock_request";

	private const string RpcDiscoveryUnlockListRequest = "keith.valheim.portallimit.discovery_unlock_list_request";

	private const string RpcDiscoveryUnlockListResponse = "keith.valheim.portallimit.discovery_unlock_list_response";

	private const string RpcAdminReportRequest = "keith.valheim.portallimit.admin_report_request";

	private const string RpcAdminReportResponse = "keith.valheim.portallimit.admin_report_response";

	private const string RpcPortalMetadataUpdate = "keith.valheim.portallimit.portal_metadata_update";

	private const string DiscoveryRoleKey = "PortalLimit_DiscoveryRole";

	private const string DiscoveryLinkKey = "PortalLimit_DiscoveryLink";

	private const string DiscoveryColorKey = "PortalLimit_DiscoveryColor";

	private const string DiscoveryMarkerTextKey = "PortalLimit_DiscoveryMarkerText";

	private const string DiscoveryShowMarkerKey = "PortalLimit_DiscoveryShowMarker";

	private const string DiscoverySuppressGlowKey = "PortalLimit_DiscoverySuppressGlow";

	private const string DiscoveryLockedHoverTextKey = "PortalLimit_DiscoveryLockedHoverText";

	private const string DiscoveryRemoteHoverTextKey = "PortalLimit_DiscoveryRemoteHoverText";

	private const string DiscoveryUnlockedHoverTextKey = "PortalLimit_DiscoveryUnlockedHoverText";

	private const string PortalBuiltUtcKey = "PortalLimit_BuiltUtc";

	private const int DiscoveryRoleNone = 0;

	private const int DiscoveryRoleLocked = 1;

	private const int DiscoveryRoleRemote = 2;

	internal static PortalLimitClientPlugin Instance;

	internal static ManualLogSource Log;

	private static readonly FieldInfo AllPiecesField = AccessTools.Field(typeof(Piece), "s_allPieces");

	private static readonly FieldInfo TeleportWorldTargetFoundField = AccessTools.Field(typeof(TeleportWorld), "m_target_found");

	private static readonly MethodInfo GetPlayerIdMethod = AccessTools.Method(typeof(Player), "GetPlayerID", (Type[])null, (Type[])null);

	private static readonly MethodInfo GetPrefabNameMethod = AccessTools.Method(typeof(ZNetView), "GetPrefabName", (Type[])null, (Type[])null);

	private static readonly Color LockedOrange = new Color(1f, 0.6235294f, 0.1019608f, 1f);

	private static readonly string[] MarkerColorLabels = new string[8] { "RED", "ORANGE", "YELLOW", "GREEN", "BLUE", "PURPLE", "WHITE", "BLACK" };

	private static readonly string[] MarkerColorValues = new string[8] { "#ff3030", "#ff9f1a", "#ffd84a", "#42ff68", "#38a6ff", "#b05cff", "#ffffff", "#000000" };

	private static readonly string[] PortalRoleChoiceLabels = new string[3] { "NORMAL PORTAL", "LOCKED ENTRANCE", "UNLOCKED ENTRANCE" };

	private static readonly string[] MarkerTextLabels = new string[3] { "UNLOCK ON OTHER SIDE", "LOCKED", "X" };

	private static readonly string[] MarkerTextValues = new string[3] { "UnlockOnOtherSide", "Locked", "X" };

	private Harmony harmony;

	private bool rpcRegistered;

	private bool configRequested;

	private bool showCountMessageOnNextResponse;

	private bool showPlacementCountOnNextResponse;

	private bool openAdminUiAfterPermissionCheck;

	private bool adminStatusKnown;

	private bool isAdmin;

	private bool adminBypass = true;

	private bool enforceOnServer = true;

	private int maxPortalsPerPlayer = 10;

	private string portalPrefabNames = "portal_wood,portal_stone";

	private bool discoveryEnabled = true;

	private bool discoveryAdminBypass = true;

	private bool adminHoldEToEdit = true;

	private bool showDiscoveryHoverText = true;

	private string discoveryLockedHoverText = "Locked: find the other end first.";

	private string discoveryRemoteHoverText = "Discovery portal: use this side to unlock the route.";

	private string discoveryUnlockedHoverText = "Discovery route unlocked.";

	private string discoveryLockedMessage = "This portal is locked. Find and enter the other end first.";

	private string discoveryUnlockMessage = "Discovery portal unlocked.";

	private bool discoveryShowWorldMarker = true;

	private bool discoveryTintPortalModel;

	private bool discoverySuppressLockedGlow;

	private string discoveryLockedColor = "#ff9f1a";

	private string discoveryRemoteColor = "#38a6ff";

	private string discoveryUnlockedColor = "#42ff68";

	private readonly HashSet<string> unlockedDiscoveryLinks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private int serverPortalCount;

	private Rect adminWindow = new Rect(120f, 60f, 700f, 720f);

	private Vector2 adminScroll;

	private Vector2 portalInfoThisScroll;

	private Vector2 portalInfoWorldScroll;

	private bool showAdminWindow;

	private int adminTab;

	private string openDropdown = string.Empty;

	private bool portalInfoShowThis;

	private bool portalInfoShowWorld;

	private string portalInfoThisText = "Open this tab while editing a portal.";

	private string portalInfoWorldText = "Click Refresh to load world portal information.";

	private string portalInfoRequestedPortalKey = string.Empty;

	private int selectedPortalRole;

	private string loadedPortalKey = string.Empty;

	private TeleportWorld selectedPortal;

	private string maxText = "10";

	private string portalNamesText = "portal_wood,portal_stone";

	private string discoveryLinkText = string.Empty;

	private string selectedPortalColorText = "#ff9f1a";

	private int selectedPortalColorIndex;

	private string selectedPortalMarkerText = "UNLOCK ON OTHER SIDE";

	private bool selectedPortalShowMarker = true;

	private bool selectedPortalSuppressGlow;

	private string selectedLockedHoverText = string.Empty;

	private string selectedRemoteHoverText = string.Empty;

	private string selectedUnlockedHoverText = string.Empty;

	private string discoveryLockedHoverTextField = "Locked: find the other end first.";

	private string discoveryRemoteHoverTextField = "Discovery portal: use this side to unlock the route.";

	private string discoveryUnlockedHoverTextField = "Discovery route unlocked.";

	private string discoveryLockedMessageField = "This portal is locked. Find and enter the other end first.";

	private string discoveryUnlockMessageField = "Discovery portal unlocked.";

	private int selectedPortalMarkerTextIndex;

	private TeleportWorld holdInteractPortal;

	private float holdInteractStartedAt;

	private bool holdInteractOpened;

	private bool replayingVanillaPortalInteract;

	private string discoveryLockedColorField = "#ff9f1a";

	private string discoveryRemoteColorField = "#38a6ff";

	private string discoveryUnlockedColorField = "#42ff68";

	private string statusMessage = "Use /portallimit to load server settings.";

	private bool adminStylesReady;

	private Texture2D adminWindowTexture;

	private Texture2D adminHeaderTexture;

	private Texture2D adminSectionTexture;

	private Texture2D adminInputTexture;

	private Texture2D adminButtonTexture;

	private Texture2D adminButtonHoverTexture;

	private Texture2D adminTabTexture;

	private Texture2D adminTabSelectedTexture;

	private GUIStyle adminWindowStyle;

	private GUIStyle adminTitleStyle;

	private GUIStyle adminLabelStyle;

	private GUIStyle adminHelpStyle;

	private GUIStyle adminSectionStyle;

	private GUIStyle adminButtonStyle;

	private GUIStyle adminTabStyle;

	private GUIStyle adminTabSelectedStyle;

	private GUIStyle adminTextFieldStyle;

	private GUIStyle adminBoxStyle;

	private ConfigEntry<KeyCode> toggleAdminUiKey;

	private void Awake()
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Expected O, but got Unknown
		if (IsDedicatedServerProcess())
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Portal Limit Client disabled on the dedicated server process.");
			((Behaviour)this).enabled = false;
			return;
		}
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		toggleAdminUiKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "ToggleAdminUIKey", (KeyCode)291, "Key used to open the portal limit admin UI.");
		harmony = new Harmony("keith.valheim.portallimit.client");
		harmony.PatchAll();
		RegisterConsoleCommands();
		((MonoBehaviour)this).InvokeRepeating("TryRegisterRpcs", 1f, 1f);
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		DestroyAdminTexture(adminWindowTexture);
		DestroyAdminTexture(adminHeaderTexture);
		DestroyAdminTexture(adminSectionTexture);
		DestroyAdminTexture(adminInputTexture);
		DestroyAdminTexture(adminButtonTexture);
		DestroyAdminTexture(adminButtonHoverTexture);
		DestroyAdminTexture(adminTabTexture);
		DestroyAdminTexture(adminTabSelectedTexture);
	}

	private static bool IsDedicatedServerProcess()
	{
		string processName = Process.GetCurrentProcess().ProcessName;
		return processName.StartsWith("valheim_server", StringComparison.OrdinalIgnoreCase);
	}

	private void TryRegisterRpcs()
	{
		if (!rpcRegistered && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.config_response", (Action<long, string>)OnConfigResponse);
			ZRoutedRpc.instance.Register<int, int, bool, string>("keith.valheim.portallimit.count_response", (Method<int, int, bool, string>)OnCountResponse);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.notice", (Action<long, string>)OnNotice);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.discovery_unlock_list_response", (Action<long, string>)OnDiscoveryUnlockListResponse);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.admin_report_response", (Action<long, string>)OnAdminReportResponse);
			rpcRegistered = true;
			((MonoBehaviour)this).CancelInvoke("TryRegisterRpcs");
		}
	}

	private static void RegisterConsoleCommands()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand("portallimit", "Portal Limit commands. Use: portallimit, portallimit count, portallimit counts, portallimit portals, portallimit export", new ConsoleEvent(OnConsolePortalLimit), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}

	private static void OnConsolePortalLimit(ConsoleEventArgs args)
	{
		if ((Object)(object)Instance == (Object)null)
		{
			Terminal.Log((object)"Portal Limit client is not ready.");
			return;
		}
		string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "help");
		if (text == "help")
		{
			Terminal.Log((object)"Portal Limit F5 commands:");
			Terminal.Log((object)"  portallimit count   - show your portal count");
			Terminal.Log((object)"  portallimit counts  - admin: show counts for all players");
			Terminal.Log((object)"  portallimit portals - admin: list known portals with names and coordinates");
			Terminal.Log((object)"  portallimit export  - admin: rewrite report files on the server");
			Terminal.Log((object)"Regular players can also type /portals in normal chat.");
		}
		else if (text == "count")
		{
			Instance.showCountMessageOnNextResponse = true;
			Instance.RequestCount();
			Terminal.Log((object)"Portal Limit: checking your portal count...");
		}
		else if (text != "counts" && text != "portals" && text != "export")
		{
			Terminal.Log((object)"Unknown Portal Limit command. Use: portallimit");
		}
		else if (ZRoutedRpc.instance == null)
		{
			Terminal.Log((object)"Portal Limit server RPC is not ready yet.");
		}
		else
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.admin_report_request", new object[1] { text });
			Terminal.Log((object)("Portal Limit: requesting " + text + " report..."));
		}
	}

	private void Update()
	{
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		if (rpcRegistered && !((Object)(object)ZNet.instance == (Object)null))
		{
			if (!configRequested && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				RequestConfig(openUi: false);
			}
			if (Input.GetKeyDown(toggleAdminUiKey.Value))
			{
				ToggleAdminWindow();
			}
			UpdateHoldInteractAdminOpen();
		}
	}

	private void UpdateHoldInteractAdminOpen()
	{
		if (!isAdmin || !adminHoldEToEdit || showAdminWindow || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			ResetHoldInteract();
		}
		else if ((Object)(object)holdInteractPortal == (Object)null)
		{
			TeleportWorld hoveredPortal = GetHoveredPortal();
			if ((Object)(object)hoveredPortal != (Object)null && Input.GetKey((KeyCode)101))
			{
				BeginDelayedPortalInteract(hoveredPortal);
			}
		}
		else if (!Input.GetKey((KeyCode)101))
		{
			TeleportWorld val = holdInteractPortal;
			bool flag = !holdInteractOpened && Time.unscaledTime - holdInteractStartedAt < 0.45f;
			ResetHoldInteract();
			if (flag && (Object)(object)val != (Object)null)
			{
				ReplayVanillaPortalInteract(val);
			}
		}
		else if (!holdInteractOpened && Time.unscaledTime - holdInteractStartedAt >= 0.45f)
		{
			holdInteractOpened = true;
			OpenAdminWindowForPortal(holdInteractPortal);
		}
	}

	private void BeginDelayedPortalInteract(TeleportWorld portal)
	{
		holdInteractPortal = portal;
		holdInteractStartedAt = Time.unscaledTime;
		holdInteractOpened = false;
	}

	private void ResetHoldInteract()
	{
		holdInteractPortal = null;
		holdInteractStartedAt = 0f;
		holdInteractOpened = false;
	}

	private void ReplayVanillaPortalInteract(TeleportWorld portal)
	{
		if ((Object)(object)portal == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		replayingVanillaPortalInteract = true;
		try
		{
			portal.Interact((Humanoid)(object)Player.m_localPlayer, false, false);
		}
		finally
		{
			replayingVanillaPortalInteract = false;
		}
	}

	private void OnGUI()
	{
		//IL_0025: 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_0045: Expected O, but got Unknown
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		if (showAdminWindow)
		{
			EnsureAdminStyles();
			CenterAdminWindowIfNeeded();
			adminWindow = GUI.Window(((Object)this).GetInstanceID(), adminWindow, new WindowFunction(DrawAdminWindow), GUIContent.none, GUIStyle.none);
		}
	}

	private void CenterAdminWindowIfNeeded()
	{
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		float num = Mathf.Min(760f, (float)Screen.width - 40f);
		float num2 = Mathf.Min(680f, (float)Screen.height - 40f);
		if (Math.Abs(((Rect)(ref adminWindow)).width - num) > 0.1f || Math.Abs(((Rect)(ref adminWindow)).height - num2) > 0.1f)
		{
			adminWindow = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2);
		}
	}

	private void DrawAdminWindow(int id)
	{
		//IL_0028: 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_008e: 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)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Unknown result type (might be due to invalid IL or missing references)
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		EnsureAdminStyles();
		GUI.Box(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, ((Rect)(ref adminWindow)).height), GUIContent.none, adminWindowStyle);
		GUI.Box(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, 42f), GUIContent.none, adminSectionStyle);
		GUI.Label(new Rect(18f, 9f, ((Rect)(ref adminWindow)).width - 36f, 28f), "Portal Limit Admin", adminTitleStyle);
		if (GUI.Button(new Rect(((Rect)(ref adminWindow)).width - 38f, 8f, 28f, 26f), "X", adminButtonStyle))
		{
			showAdminWindow = false;
			return;
		}
		GUILayout.BeginArea(new Rect(16f, 48f, ((Rect)(ref adminWindow)).width - 32f, ((Rect)(ref adminWindow)).height - 62f));
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(statusMessage, adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (isAdmin)
		{
			DrawAdminTabs();
			adminScroll = GUILayout.BeginScrollView(adminScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(Math.Max(220f, ((Rect)(ref adminWindow)).height - 145f)) });
			if (adminTab == 0)
			{
				DrawServerSettingsTab();
			}
			else if (adminTab == 1)
			{
				DrawPortalSettingsTab();
			}
			else
			{
				DrawPortalInformationTab();
			}
			GUILayout.EndScrollView();
			if (adminTab == 2)
			{
				DrawInformationFooter();
			}
			else
			{
				DrawAdminFooter();
			}
		}
		else
		{
			GUILayout.Label("You need to be on the Valheim admin list to edit this.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (GUILayout.Button("Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				showAdminWindow = false;
			}
		}
		GUILayout.EndVertical();
		GUILayout.EndArea();
		GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref adminWindow)).width, 42f));
	}

	private void EnsureAdminStyles()
	{
		//IL_0029: 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_0071: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Expected O, but got Unknown
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Expected O, but got Unknown
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a6: Expected O, but got Unknown
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Expected O, but got Unknown
		//IL_0203: Unknown result type (might be due to invalid IL or missing references)
		//IL_0219: Unknown result type (might be due to invalid IL or missing references)
		//IL_0223: Expected O, but got Unknown
		//IL_025d: Unknown result type (might be due to invalid IL or missing references)
		//IL_026f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0279: Expected O, but got Unknown
		//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c6: Expected O, but got Unknown
		//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f1: Expected O, but got Unknown
		//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0306: Expected O, but got Unknown
		//IL_0326: Unknown result type (might be due to invalid IL or missing references)
		//IL_0364: Unknown result type (might be due to invalid IL or missing references)
		//IL_036e: Expected O, but got Unknown
		//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0412: Unknown result type (might be due to invalid IL or missing references)
		//IL_0435: Unknown result type (might be due to invalid IL or missing references)
		//IL_043f: Expected O, but got Unknown
		//IL_044a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0454: Expected O, but got Unknown
		//IL_045c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0466: Expected O, but got Unknown
		//IL_0491: Unknown result type (might be due to invalid IL or missing references)
		//IL_049b: Expected O, but got Unknown
		//IL_04bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ea: Expected O, but got Unknown
		//IL_0539: Unknown result type (might be due to invalid IL or missing references)
		//IL_0543: Expected O, but got Unknown
		//IL_0563: Unknown result type (might be due to invalid IL or missing references)
		//IL_0579: Unknown result type (might be due to invalid IL or missing references)
		//IL_059e: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a8: Expected O, but got Unknown
		//IL_05b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_05be: Expected O, but got Unknown
		//IL_05ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_05d4: Expected O, but got Unknown
		//IL_05f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_05ff: Expected O, but got Unknown
		//IL_060a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0614: Expected O, but got Unknown
		if (!adminStylesReady)
		{
			adminWindowTexture = MakeAdminTexture(new Color(0.03f, 0.026f, 0.022f, 0.98f));
			adminHeaderTexture = MakeAdminTexture(new Color(0.18f, 0.115f, 0.065f, 0.98f));
			adminSectionTexture = MakeAdminTexture(new Color(0.13f, 0.085f, 0.052f, 0.96f));
			adminInputTexture = MakeAdminBorderTexture(new Color(0.12f, 0.105f, 0.08f, 1f), new Color(1f, 0.78f, 0.26f, 1f));
			adminButtonTexture = MakeAdminTexture(new Color(0.25f, 0.24f, 0.21f, 0.98f));
			adminButtonHoverTexture = MakeAdminTexture(new Color(0.36f, 0.31f, 0.23f, 0.98f));
			adminTabTexture = MakeAdminTexture(new Color(0.16f, 0.14f, 0.12f, 0.98f));
			adminTabSelectedTexture = MakeAdminTexture(new Color(0.45f, 0.31f, 0.14f, 0.98f));
			adminWindowStyle = new GUIStyle(GUI.skin.box);
			adminWindowStyle.normal.background = adminWindowTexture;
			adminWindowStyle.border = new RectOffset(8, 8, 8, 8);
			adminWindowStyle.padding = new RectOffset(14, 14, 14, 14);
			adminTitleStyle = new GUIStyle(GUI.skin.label);
			adminTitleStyle.fontSize = 18;
			adminTitleStyle.fontStyle = (FontStyle)1;
			adminTitleStyle.alignment = (TextAnchor)4;
			adminTitleStyle.normal.textColor = new Color(0.95f, 0.83f, 0.55f, 1f);
			adminLabelStyle = new GUIStyle(GUI.skin.label);
			adminLabelStyle.fontSize = 14;
			adminLabelStyle.wordWrap = true;
			adminLabelStyle.normal.textColor = new Color(0.88f, 0.84f, 0.74f, 1f);
			adminHelpStyle = new GUIStyle(adminLabelStyle);
			adminHelpStyle.fontSize = 13;
			adminHelpStyle.normal.textColor = new Color(0.67f, 0.64f, 0.56f, 1f);
			adminSectionStyle = new GUIStyle(GUI.skin.box);
			adminSectionStyle.normal.background = adminSectionTexture;
			adminSectionStyle.border = new RectOffset(4, 4, 4, 4);
			adminSectionStyle.padding = new RectOffset(8, 8, 4, 4);
			adminSectionStyle.normal.textColor = new Color(0.98f, 0.82f, 0.43f, 1f);
			adminSectionStyle.alignment = (TextAnchor)3;
			adminSectionStyle.fontStyle = (FontStyle)1;
			adminSectionStyle.fontSize = 15;
			adminButtonStyle = new GUIStyle(GUI.skin.button);
			adminButtonStyle.normal.background = adminButtonTexture;
			adminButtonStyle.hover.background = adminButtonHoverTexture;
			adminButtonStyle.active.background = adminTabSelectedTexture;
			adminButtonStyle.normal.textColor = new Color(0.9f, 0.86f, 0.76f, 1f);
			adminButtonStyle.hover.textColor = new Color(1f, 0.9f, 0.58f, 1f);
			adminButtonStyle.active.textColor = Color.white;
			adminButtonStyle.fontSize = 13;
			adminButtonStyle.padding = new RectOffset(8, 8, 5, 5);
			adminButtonStyle.margin = new RectOffset(3, 3, 3, 3);
			adminTabStyle = new GUIStyle(adminButtonStyle);
			adminTabStyle.normal.background = adminTabTexture;
			adminTabStyle.fontStyle = (FontStyle)1;
			adminTabSelectedStyle = new GUIStyle(adminButtonStyle);
			adminTabSelectedStyle.normal.background = adminTabSelectedTexture;
			adminTabSelectedStyle.normal.textColor = Color.white;
			adminTabSelectedStyle.fontStyle = (FontStyle)1;
			adminTextFieldStyle = new GUIStyle(GUI.skin.textField);
			adminTextFieldStyle.normal.background = adminInputTexture;
			adminTextFieldStyle.focused.background = adminInputTexture;
			adminTextFieldStyle.hover.background = adminInputTexture;
			adminTextFieldStyle.border = new RectOffset(4, 4, 4, 4);
			adminTextFieldStyle.normal.textColor = new Color(1f, 0.98f, 0.86f, 1f);
			adminTextFieldStyle.focused.textColor = Color.white;
			adminTextFieldStyle.fontSize = 15;
			adminTextFieldStyle.padding = new RectOffset(12, 12, 8, 8);
			adminTextFieldStyle.margin = new RectOffset(3, 3, 5, 10);
			adminBoxStyle = new GUIStyle(GUI.skin.box);
			adminBoxStyle.normal.background = adminHeaderTexture;
			adminBoxStyle.padding = new RectOffset(6, 6, 6, 6);
			adminBoxStyle.margin = new RectOffset(0, 0, 2, 8);
			adminStylesReady = true;
		}
	}

	private static Texture2D MakeAdminTexture(Color color)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
		val.SetPixel(0, 0, color);
		val.Apply();
		return val;
	}

	private static Texture2D MakeAdminBorderTexture(Color fill, Color border)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Expected O, but got Unknown
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(16, 16, (TextureFormat)4, false);
		for (int i = 0; i < 16; i++)
		{
			for (int j = 0; j < 16; j++)
			{
				bool flag = j < 2 || i < 2 || j > 13 || i > 13;
				val.SetPixel(j, i, flag ? border : fill);
			}
		}
		val.Apply();
		return val;
	}

	private static void DestroyAdminTexture(Texture2D texture)
	{
		if ((Object)(object)texture != (Object)null)
		{
			Object.Destroy((Object)(object)texture);
		}
	}

	private void DrawAdminTabs()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Server Settings", (adminTab == 0) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 0;
			openDropdown = string.Empty;
		}
		if (GUILayout.Button("This Portal", (adminTab == 1) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 1;
			openDropdown = string.Empty;
		}
		if (GUILayout.Button("Portal Information", (adminTab == 2) ? adminTabSelectedStyle : adminTabStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			adminTab = 2;
			openDropdown = string.Empty;
			RequestPortalInformation(showStatus: false);
		}
		GUILayout.EndHorizontal();
	}

	private void DrawSectionHeader(string text)
	{
		GUILayout.Box(text, adminSectionStyle, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.ExpandWidth(true),
			GUILayout.Height(30f)
		});
	}

	private void DrawServerSettingsTab()
	{
		GUILayout.Space(8f);
		DrawSectionHeader("Portal Limit");
		GUILayout.Label("How many counted portals may each non-admin player own? Use 0 for no limit.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		maxText = GUILayout.TextField(maxText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		adminBypass = DrawBoolDropdown("adminBypass", "Admin portal limit bypass", adminBypass, "Allowed", "Not allowed");
		enforceOnServer = DrawBoolDropdown("enforceOnServer", "Server removes extra portals over the limit", enforceOnServer, "Enabled", "Disabled");
		GUILayout.Label("Which portal types count toward the limit?", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Default covers normal wood and stone portals. Empty means every portal-like object.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		portalNamesText = GUILayout.TextField(portalNamesText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Your current portal count: " + serverPortalCount + " / " + maxPortalsPerPlayer, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(12f);
		DrawSectionHeader("Discovery Portals");
		GUILayout.Label("These settings apply to every locked discovery portal pair.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryEnabled = DrawBoolDropdown("discoveryEnabled", "Locked discovery portals", discoveryEnabled, "Enabled", "Disabled");
		discoveryAdminBypass = DrawBoolDropdown("discoveryAdminBypass", "Admin bypass for locked discovery portals", discoveryAdminBypass, "Allowed", "Not allowed");
		adminHoldEToEdit = DrawBoolDropdown("adminHoldEToEdit", "Admin hold E portal editor", adminHoldEToEdit, "Enabled", "Disabled");
		GUILayout.Label("When enabled, admins can hold E on a portal to edit that portal. Tap E still opens Valheim's normal Set Tag box.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		showDiscoveryHoverText = DrawBoolDropdown("showDiscoveryHoverText", "Lock/unlock hover text", showDiscoveryHoverText, "Shown", "Hidden");
		GUILayout.Space(12f);
		DrawSectionHeader("Default Player-Facing Hover Text");
		GUILayout.Label("These are the server defaults. A locked portal can override them on its own This Portal tab.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Locked entrance text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryLockedHoverTextField = GUILayout.TextField(discoveryLockedHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Unlocked entrance text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryRemoteHoverTextField = GUILayout.TextField(discoveryRemoteHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("After unlocked text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		discoveryUnlockedHoverTextField = GUILayout.TextField(discoveryUnlockedHoverTextField, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
	}

	private void DrawPortalSettingsTab()
	{
		GUILayout.Space(8f);
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			DrawSectionHeader("This Portal");
			GUILayout.Label("No portal was opened.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Look at a portal and hold E to open settings for that exact portal.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			return;
		}
		int discoveryRole = GetDiscoveryRole(val);
		string portalTagForUi = GetPortalTagForUi(val);
		GUILayout.Label("Portal tag", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Use the same tag on both portals, for example: Bog Witch.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (CanEditPortalTag(val))
		{
			discoveryLinkText = GUILayout.TextField(discoveryLinkText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			GUILayout.Label(discoveryLinkText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("This portal was built by another player. Admins can edit Portal Limit settings, but the portal tag stays protected.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUILayout.Space(8f);
		DrawSectionHeader("This portal");
		GUILayout.Label("Current: " + GetPortalRoleLabel(discoveryRole).ToUpperInvariant() + "  |  Tag: " + (string.IsNullOrWhiteSpace(portalTagForUi) ? "(NONE)" : portalTagForUi), adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		selectedPortalRole = DrawInlineChoice("selectedPortalRole", selectedPortalRole, PortalRoleChoiceLabels);
		int selectedPortalRoleValue = GetSelectedPortalRoleValue();
		if (selectedPortalRoleValue != 0)
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Relock Portal Tag");
			GUILayout.Label("For this portal tag, make every player discover the unlocked entrance again.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			bool flag2 = (GUI.enabled = !string.IsNullOrWhiteSpace(discoveryLinkText));
			if (GUILayout.Button("Relock this portal tag for players", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				RelockSelectedDiscoveryPortal();
			}
			GUI.enabled = true;
			if (!flag2)
			{
				GUILayout.Label("Enter a portal tag before relocking players.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
		switch (selectedPortalRoleValue)
		{
		case 1:
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Locked Entrance Options");
			selectedPortalShowMarker = DrawBoolInline("Unlock message marker", selectedPortalShowMarker, "SHOWN", "HIDDEN");
			selectedPortalSuppressGlow = !DrawBoolInline("Orange glow while locked", !selectedPortalSuppressGlow, "SHOWN", "HIDDEN");
			GUILayout.Space(8f);
			GUILayout.Label("Marker color", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			int num = selectedPortalColorIndex;
			selectedPortalColorIndex = DrawInlineChoice("selectedPortalColor", selectedPortalColorIndex, MarkerColorLabels);
			if (num != selectedPortalColorIndex)
			{
				selectedPortalColorText = MarkerColorValues[Mathf.Clamp(selectedPortalColorIndex, 0, MarkerColorValues.Length - 1)];
			}
			selectedPortalColorText = GUILayout.TextField(selectedPortalColorText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
			GUILayout.Label("Marker text", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			int num2 = selectedPortalMarkerTextIndex;
			selectedPortalMarkerTextIndex = DrawInlineChoice("selectedPortalMarkerText", selectedPortalMarkerTextIndex, MarkerTextLabels);
			if (num2 != selectedPortalMarkerTextIndex)
			{
				selectedPortalMarkerText = MarkerTextLabels[Mathf.Clamp(selectedPortalMarkerTextIndex, 0, MarkerTextLabels.Length - 1)];
			}
			selectedPortalMarkerText = GUILayout.TextField(selectedPortalMarkerText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
			GUILayout.Label("Player-facing hover text for this portal", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedLockedHoverText = GUILayout.TextField(selectedLockedHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			break;
		}
		case 2:
			GUILayout.Space(10f);
			DrawSectionHeader("Unlocked Entrance Options");
			GUILayout.Label("Player-facing hover text for this portal", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedRemoteHoverText = GUILayout.TextField(selectedRemoteHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			break;
		}
		if (selectedPortalRoleValue != 0)
		{
			GUILayout.Space(10f);
			DrawSectionHeader("Unlocked Message");
			GUILayout.Label("Text players see after they have unlocked this portal tag. Leave blank to use the server default.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			selectedUnlockedHoverText = GUILayout.TextField(selectedUnlockedHoverText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
	}

	private void DrawPortalInformationTab()
	{
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: 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_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(8f);
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			DrawSectionHeader("Portal Information");
			GUILayout.Label("No portal was opened.", adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label("Look at a portal and hold E to open settings for that exact portal.", adminHelpStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			return;
		}
		if (GUILayout.Button("Refresh portal information", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			RequestPortalInformation(showStatus: true);
		}
		portalInfoShowThis = DrawCollapseHeader(portalInfoShowThis, "THIS PORTAL");
		if (portalInfoShowThis)
		{
			portalInfoThisScroll = GUILayout.BeginScrollView(portalInfoThisScroll, adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(220f) });
			GUILayout.TextArea(portalInfoThisText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(GetTextAreaHeight(portalInfoThisText, 170f)) });
			GUILayout.EndScrollView();
		}
		portalInfoShowWorld = DrawCollapseHeader(portalInfoShowWorld, "WORLD PORTALS");
		if (portalInfoShowWorld)
		{
			portalInfoWorldScroll = GUILayout.BeginScrollView(portalInfoWorldScroll, adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(260f) });
			GUILayout.TextArea(portalInfoWorldText, adminTextFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(GetTextAreaHeight(portalInfoWorldText, 220f)) });
			GUILayout.EndScrollView();
		}
	}

	private static float GetTextAreaHeight(string text, float minimum)
	{
		int num = 1;
		if (!string.IsNullOrEmpty(text))
		{
			num = text.Split(new char[1] { '\n' }).Length;
		}
		return Math.Max(minimum, 22f + (float)num * 20f);
	}

	private bool DrawCollapseHeader(bool expanded, string label)
	{
		GUILayout.Space(8f);
		if (GUILayout.Button((expanded ? "v " : "> ") + label, adminTabSelectedStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			expanded = !expanded;
		}
		return expanded;
	}

	private bool DrawBoolInline(string label, bool value, string trueLabel, string falseLabel)
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(label, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) });
		if (GUILayout.Button((value ? "> " : string.Empty) + trueLabel, value ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			value = true;
		}
		if (GUILayout.Button(((!value) ? "> " : string.Empty) + falseLabel, (!value) ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			value = false;
		}
		GUILayout.EndHorizontal();
		return value;
	}

	private int DrawInlineChoice(string id, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		for (int i = 0; i < labels.Length; i++)
		{
			if (GUILayout.Button(((i == selected) ? "> " : string.Empty) + labels[i], (i == selected) ? adminTabSelectedStyle : adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				selected = i;
				openDropdown = string.Empty;
			}
		}
		GUILayout.EndHorizontal();
		return selected;
	}

	private bool DrawBoolDropdown(string id, string label, bool value, string trueLabel, string falseLabel)
	{
		int selected = ((!value) ? 1 : 0);
		selected = DrawChoiceDropdown(id, label, selected, new string[2] { trueLabel, falseLabel });
		return selected == 0;
	}

	private int DrawChoiceDropdown(string id, int selected, string[] labels)
	{
		return DrawChoiceDropdown(id, string.Empty, selected, labels);
	}

	private int DrawChoiceDropdown(string id, string label, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (!string.IsNullOrWhiteSpace(label))
		{
			GUILayout.Label(label, adminLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) });
		}
		string text = labels[selected] + "  v";
		if (GUILayout.Button(text, adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(190f) }))
		{
			openDropdown = ((openDropdown == id) ? string.Empty : id);
		}
		GUILayout.EndHorizontal();
		if (openDropdown == id)
		{
			GUILayout.BeginVertical(adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			for (int i = 0; i < labels.Length; i++)
			{
				string text2 = ((i == selected) ? "> " : "  ");
				if (GUILayout.Button(text2 + labels[i], adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
				{
					selected = i;
					openDropdown = string.Empty;
				}
			}
			GUILayout.EndVertical();
		}
		return selected;
	}

	private int DrawChoiceList(string id, int selected, string[] labels)
	{
		if (labels == null || labels.Length == 0)
		{
			return selected;
		}
		selected = Mathf.Clamp(selected, 0, labels.Length - 1);
		GUILayout.BeginVertical(adminBoxStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		for (int i = 0; i < labels.Length; i++)
		{
			string text = ((i == selected) ? "> " : "  ");
			if (GUILayout.Button(text + labels[i], adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				selected = i;
				openDropdown = string.Empty;
			}
		}
		GUILayout.EndVertical();
		return selected;
	}

	private void DrawAdminFooter()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Apply", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			ApplyActiveTab(closeAfterApply: false);
		}
		if (GUILayout.Button("Apply & Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			ApplyActiveTab(closeAfterApply: true);
		}
		if (GUILayout.Button("Close Without Applying", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			showAdminWindow = false;
		}
		GUILayout.EndHorizontal();
	}

	private void DrawInformationFooter()
	{
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Refresh", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			RequestPortalInformation(showStatus: true);
		}
		if (GUILayout.Button("Close", adminButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			showAdminWindow = false;
		}
		GUILayout.EndHorizontal();
	}

	private void ApplyActiveTab(bool closeAfterApply)
	{
		if (adminTab == 0)
		{
			SaveConfigFromUi();
		}
		else
		{
			MarkSelectedDiscoveryPortal(GetSelectedPortalRoleValue());
		}
		if (closeAfterApply)
		{
			showAdminWindow = false;
		}
	}

	private void ToggleAdminWindow()
	{
		if (!adminStatusKnown)
		{
			openAdminUiAfterPermissionCheck = true;
			RequestConfig(openUi: false);
			ShowMessage("Checking portal limit permissions...");
			return;
		}
		if (!isAdmin)
		{
			showAdminWindow = false;
			RequestCountForChat();
			return;
		}
		showAdminWindow = !showAdminWindow;
		openDropdown = string.Empty;
		if (showAdminWindow)
		{
			RequestConfig(openUi: true);
			RequestCount();
			CenterAdminWindowIfNeeded();
		}
	}

	private void OpenAdminWindowForPortal(TeleportWorld portal)
	{
		SelectPortalForEditing(portal, showStatus: false);
		showAdminWindow = true;
		openDropdown = string.Empty;
		adminTab = 1;
		statusMessage = "Portal selected. Choose what this portal should do, then Apply or Apply & Close.";
		CenterAdminWindowIfNeeded();
		RequestConfig(openUi: true);
		RequestCount();
	}

	private void SaveConfigFromUi()
	{
		if (!int.TryParse(maxText, out var result))
		{
			statusMessage = "Max portals must be a whole number.";
			return;
		}
		string text = "max=" + Math.Max(0, result) + "\nadminBypass=" + adminBypass + "\nenforceOnServer=" + enforceOnServer + "\nportalPrefabNames=" + Escape(portalNamesText) + "\ndiscoveryEnabled=" + discoveryEnabled + "\ndiscoveryAdminBypass=" + discoveryAdminBypass + "\nadminHoldEToEdit=" + adminHoldEToEdit + "\nshowDiscoveryHoverText=" + showDiscoveryHoverText + "\ndiscoveryLockedHoverText=" + Escape(discoveryLockedHoverTextField) + "\ndiscoveryRemoteHoverText=" + Escape(discoveryRemoteHoverTextField) + "\ndiscoveryUnlockedHoverText=" + Escape(discoveryUnlockedHoverTextField) + "\ndiscoveryLockedMessage=" + Escape(discoveryLockedMessageField) + "\ndiscoveryUnlockMessage=" + Escape(discoveryUnlockMessageField) + "\ndiscoveryShowWorldMarker=" + discoveryShowWorldMarker + "\ndiscoveryTintPortalModel=" + discoveryTintPortalModel + "\ndiscoverySuppressLockedGlow=" + discoverySuppressLockedGlow + "\ndiscoveryLockedColor=" + Escape(discoveryLockedColorField) + "\ndiscoveryRemoteColor=" + Escape(discoveryRemoteColorField) + "\ndiscoveryUnlockedColor=" + Escape(discoveryUnlockedColorField);
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.config_update", new object[1] { text });
		statusMessage = "Saving portal limit settings...";
	}

	private void RequestConfig(bool openUi)
	{
		configRequested = true;
		if (openUi)
		{
			statusMessage = "Loading portal limit settings...";
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.config_request", new object[1] { (openUi ? string.Empty : "[silent]") + GetPlayerName() });
		RequestUnlockList();
	}

	private void RequestCount()
	{
		long localPlayerId = GetLocalPlayerId();
		if (localPlayerId != 0)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.count_request", new object[1] { localPlayerId });
		}
	}

	private void RequestCountForChat()
	{
		showCountMessageOnNextResponse = true;
		RequestCount();
		ShowMessage("Checking portal count...");
	}

	private void RequestPlacementCountSoon()
	{
		((MonoBehaviour)this).StartCoroutine(RequestPlacementCountNextFrame());
	}

	private IEnumerator RequestPlacementCountNextFrame()
	{
		yield return (object)new WaitForSeconds(0.35f);
		long playerId = GetLocalPlayerId();
		int count = CountPortals(playerId);
		string maxText = ((maxPortalsPerPlayer > 0) ? maxPortalsPerPlayer.ToString() : "unlimited");
		serverPortalCount = count;
		ShowMessage("Portals placed: " + count + " / " + maxText);
		RequestCount();
	}

	private void RequestUnlockList()
	{
		long localPlayerId = GetLocalPlayerId();
		if (localPlayerId != 0)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_unlock_list_request", new object[1] { localPlayerId });
		}
	}

	private void RequestPortalInformation(bool showStatus)
	{
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			portalInfoThisText = "No portal was opened.";
			portalInfoWorldText = "Open this tab while editing a portal.";
			return;
		}
		ZNetView component = ((Component)val).GetComponent<ZNetView>();
		ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		portalInfoRequestedPortalKey = ((val2 != null) ? ((object)(ZDOID)(ref val2.m_uid)).ToString() : GetPortalKey(val));
		portalInfoThisText = "Loading authoritative portal information from the server...";
		string portalTagForUi = GetPortalTagForUi(val);
		if (ZRoutedRpc.instance == null)
		{
			portalInfoWorldText = "Portal Limit server RPC is not ready yet.";
			return;
		}
		if (showStatus)
		{
			statusMessage = "Loading portal information...";
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.admin_report_request", new object[1] { "portalinfo|" + Escape(portalTagForUi) + "|" + Escape(portalInfoRequestedPortalKey) });
	}

	private static void OnConfigResponse(long sender, string payload)
	{
		Instance.ApplyConfigPayload(payload);
	}

	private static void OnCountResponse(long sender, int count, int max, bool limitReached, string payload)
	{
		Instance.serverPortalCount = count;
		Instance.maxPortalsPerPlayer = max;
		Instance.ApplyConfigPayload(payload);
		if (Instance.showPlacementCountOnNextResponse)
		{
			Instance.showPlacementCountOnNextResponse = false;
			string text = ((max > 0) ? max.ToString() : "unlimited");
			Instance.ShowMessage("Portals placed: " + count + " / " + text);
		}
		else if (Instance.showCountMessageOnNextResponse)
		{
			Instance.showCountMessageOnNextResponse = false;
			string text = ((max > 0) ? max.ToString() : "unlimited");
			Instance.ShowMessage("Portals: " + count + " / " + text);
		}
	}

	private static void OnNotice(long sender, string message)
	{
		Instance.ShowMessage(message);
	}

	private static void OnDiscoveryUnlockListResponse(long sender, string payload)
	{
		Instance.ApplyUnlockPayload(payload);
	}

	private static void OnAdminReportResponse(long sender, string payload)
	{
		if (string.IsNullOrWhiteSpace(payload))
		{
			Terminal.Log((object)"Portal Limit: empty report.");
			return;
		}
		if ((Object)(object)Instance != (Object)null && payload.StartsWith("PortalInfoResponse\n", StringComparison.Ordinal))
		{
			Instance.ApplyPortalInformationPayload(payload);
			return;
		}
		string[] array = payload.Split(new char[1] { '\n' });
		foreach (string text in array)
		{
			Terminal.Log((object)text);
		}
		if ((Object)(object)Instance != (Object)null)
		{
			Instance.ShowMessage("Portal Limit report written to F5 console.");
		}
	}

	private void ApplyPortalInformationPayload(string payload)
	{
		string text = payload.Substring("PortalInfoResponse\n".Length);
		string[] array = text.Split(new string[1] { "\n---WORLD---\n" }, StringSplitOptions.None);
		if (array.Length > 0 && !string.IsNullOrWhiteSpace(array[0]))
		{
			portalInfoThisText = array[0].Trim();
		}
		portalInfoWorldText = ((array.Length > 1) ? array[1].Trim() : text.Trim());
		statusMessage = "Portal information loaded.";
	}

	private void ApplyConfigPayload(string payload)
	{
		Dictionary<string, string> dictionary = ParsePayload(payload);
		if (dictionary.TryGetValue("isAdmin", out var value) && bool.TryParse(value, out var result))
		{
			isAdmin = result;
			adminStatusKnown = true;
			if (!isAdmin)
			{
				showAdminWindow = false;
			}
			if (openAdminUiAfterPermissionCheck)
			{
				openAdminUiAfterPermissionCheck = false;
				if (isAdmin)
				{
					showAdminWindow = true;
					openDropdown = string.Empty;
					CenterAdminWindowIfNeeded();
					RequestConfig(openUi: true);
					RequestCount();
				}
				else
				{
					RequestCountForChat();
				}
			}
		}
		if (dictionary.TryGetValue("max", out value) && int.TryParse(value, out var result2))
		{
			maxPortalsPerPlayer = result2;
			maxText = result2.ToString();
		}
		if (dictionary.TryGetValue("adminBypass", out value) && bool.TryParse(value, out result))
		{
			adminBypass = result;
		}
		if (dictionary.TryGetValue("enforceOnServer", out value) && bool.TryParse(value, out result))
		{
			enforceOnServer = result;
		}
		if (dictionary.TryGetValue("portalPrefabNames", out value))
		{
			portalPrefabNames = value;
			portalNamesText = value;
		}
		if (dictionary.TryGetValue("discoveryEnabled", out value) && bool.TryParse(value, out result))
		{
			discoveryEnabled = result;
		}
		if (dictionary.TryGetValue("discoveryAdminBypass", out value) && bool.TryParse(value, out result))
		{
			discoveryAdminBypass = result;
		}
		if (dictionary.TryGetValue("adminHoldEToEdit", out value) && bool.TryParse(value, out result))
		{
			adminHoldEToEdit = result;
		}
		if (dictionary.TryGetValue("showDiscoveryHoverText", out value) && bool.TryParse(value, out result))
		{
			showDiscoveryHoverText = result;
		}
		if (dictionary.TryGetValue("discoveryLockedHoverText", out value))
		{
			discoveryLockedHoverText = value;
			discoveryLockedHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryRemoteHoverText", out value))
		{
			discoveryRemoteHoverText = value;
			discoveryRemoteHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockedHoverText", out value))
		{
			discoveryUnlockedHoverText = value;
			discoveryUnlockedHoverTextField = value;
		}
		if (dictionary.TryGetValue("discoveryLockedMessage", out value))
		{
			discoveryLockedMessage = value;
			discoveryLockedMessageField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockMessage", out value))
		{
			discoveryUnlockMessage = value;
			discoveryUnlockMessageField = value;
		}
		if (dictionary.TryGetValue("discoveryShowWorldMarker", out value) && bool.TryParse(value, out result))
		{
			discoveryShowWorldMarker = result;
		}
		if (dictionary.TryGetValue("discoveryTintPortalModel", out value) && bool.TryParse(value, out result))
		{
			discoveryTintPortalModel = result;
		}
		if (dictionary.TryGetValue("discoverySuppressLockedGlow", out value) && bool.TryParse(value, out result))
		{
			discoverySuppressLockedGlow = result;
		}
		if (dictionary.TryGetValue("discoveryLockedColor", out value))
		{
			discoveryLockedColor = value;
			discoveryLockedColorField = value;
		}
		if (dictionary.TryGetValue("discoveryRemoteColor", out value))
		{
			discoveryRemoteColor = value;
			discoveryRemoteColorField = value;
		}
		if (dictionary.TryGetValue("discoveryUnlockedColor", out value))
		{
			discoveryUnlockedColor = value;
			discoveryUnlockedColorField = value;
		}
		if (dictionary.TryGetValue("message", out value) && !string.IsNullOrWhiteSpace(value))
		{
			statusMessage = value;
			ShowMessage(value);
		}
	}

	private void ApplyUnlockPayload(string payload)
	{
		Dictionary<string, string> dictionary = ParsePayload(payload);
		unlockedDiscoveryLinks.Clear();
		if (dictionary.TryGetValue("links", out var value))
		{
			string[] array = value.Split(new char[1] { ';' });
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (!string.IsNullOrWhiteSpace(text2))
				{
					unlockedDiscoveryLinks.Add(text2);
				}
			}
		}
		if (dictionary.TryGetValue("message", out var value2) && !string.IsNullOrWhiteSpace(value2))
		{
			ShowMessage(value2);
		}
	}

	private void MarkSelectedDiscoveryPortal(int role)
	{
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			statusMessage = "No portal selected. Look at a portal and click Select first.";
			return;
		}
		ZNetView component = ((Component)val).GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsValid())
		{
			statusMessage = "That portal has no valid network view.";
			return;
		}
		string text = discoveryLinkText.Trim();
		bool flag = CanEditPortalTag(val);
		if (!flag)
		{
			text = GetPortalTagForUi(val).Trim();
		}
		if (role != 0 && string.IsNullOrWhiteSpace(text))
		{
			statusMessage = "Enter a portal tag first.";
			return;
		}
		discoveryLinkText = text;
		if (flag)
		{
			SetPortalTag(val, text);
		}
		string text2 = ((role == 0) ? string.Empty : text);
		string normalized = string.Empty;
		string text3 = string.Empty;
		if (role != 0)
		{
			if (!TryNormalizeHexColor(selectedPortalColorText, out normalized))
			{
				statusMessage = "Enter a color like #ff3030.";
				return;
			}
			selectedPortalColorText = normalized;
			text3 = NormalizeMarkerTextValue(selectedPortalMarkerText);
		}
		string text4 = "role=" + role + "\nlink=" + Escape(text2) + "\nmarkerColor=" + Escape(normalized) + "\nmarkerText=" + Escape(text3) + "\nshowMarker=" + selectedPortalShowMarker + "\nsuppressGlow=" + selectedPortalSuppressGlow + "\nlockedHoverText=" + Escape(selectedLockedHoverText) + "\nremoteHoverText=" + Escape(selectedRemoteHoverText) + "\nunlockedHoverText=" + Escape(selectedUnlockedHoverText);
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_mark_request", new object[2]
		{
			component.GetZDO().m_uid,
			text4
		});
		ApplyLocalPortalMarker(val, role, text2, normalized, text3, selectedPortalShowMarker, selectedPortalSuppressGlow, selectedLockedHoverText, selectedRemoteHoverText, selectedUnlockedHoverText);
		statusMessage = "Portal settings saved.";
		Log.LogInfo((object)("Sent discovery portal marker request role=" + role + " tag='" + text + "' portal=" + component.GetZDO().m_uid));
	}

	private void RelockSelectedDiscoveryPortal()
	{
		RelockDiscoveryPortalTag(discoveryLinkText);
	}

	private void RelockDiscoveryPortalTag(string portalTagText)
	{
		string text = (portalTagText ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			statusMessage = "Enter a portal tag first.";
			return;
		}
		ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_relock_request", new object[1] { text });
		unlockedDiscoveryLinks.Remove(text);
		statusMessage = "Relock request sent for portal tag '" + text + "'.";
	}

	private void ReadSelectedPortalSettings()
	{
		TeleportWorld val = GetSelectedPortal();
		if ((Object)(object)val == (Object)null)
		{
			statusMessage = "No portal selected. Look at a portal and click Select first.";
		}
		else
		{
			LoadPortalSettings(val, showStatus: true);
		}
	}

	private string BuildLocalPortalInformation(TeleportWorld portal)
	{
		if ((Object)(object)portal == (Object)null)
		{
			return "THIS PORTAL\nNo portal selected.";
		}
		ZNetView component = ((Component)portal).GetComponent<ZNetView>();
		ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		string text = ((val != null) ? val.GetString("PortalLimit_BuiltUtc", string.Empty) : string.Empty);
		string text2 = GetPortalCreatorName(portal);
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = "unknown";
		}
		string text3 = GetPortalTagForUi(portal);
		if (string.IsNullOrWhiteSpace(text3))
		{
			text3 = "(none)";
		}
		string text4 = (string.IsNullOrWhiteSpace(text) ? "unknown for older portals" : text);
		return "THIS PORTAL\nPortal Tag: " + text3 + "\nPlayer Who Built the Portal: " + text2 + "\nDate the portal was built: " + text4 + "\nPortal role: " + GetPortalRoleLabel(GetDiscoveryRole(portal)) + "\nPortal id: " + ((val != null) ? ((object)(ZDOID)(ref val.m_uid)).ToString() : "unknown");
	}

	private void SelectPortalForEditing(TeleportWorld portal, bool showStatus)
	{
		if ((Object)(object)portal == (Object)null)
		{
			if (showStatus)
			{
				statusMessage = "Look directly at a portal first.";
			}
		}
		else
		{
			selectedPortal = portal;
			LoadPortalSettings(portal, showStatus);
		}
	}

	private TeleportWorld GetSelectedPortal()
	{
		if ((Object)(object)selectedPortal != (Object)null)
		{
			ZNetView component = ((Component)selectedPortal).GetComponent<ZNetView>();
			if ((Object)(object)component != (Object)null && component.IsValid())
			{
				return selectedPortal;
			}
		}
		selectedPortal = null;
		loadedPortalKey = string.Empty;
		return null;
	}

	private void LoadPortalSettings(TeleportWorld portal, bool showStatus)
	{
		selectedPortalRole = GetDiscoveryRole(portal) switch
		{
			2 => 2, 
			1 => 1, 
			_ => 0, 
		};
		string discoveryLink = GetDiscoveryLink(portal);
		if (!string.IsNullOrWhiteSpace(discoveryLink))
		{
			discoveryLinkText = discoveryLink;
		}
		else
		{
			string portalTag = GetPortalTag(portal);
			if (!string.IsNullOrWhiteSpace(portalTag))
			{
				discoveryLinkText = portalTag;
			}
		}
		loadedPortalKey = GetPortalKey(portal);
		selectedPortalColorText = GetDiscoveryColor(portal);
		if (string.IsNullOrWhiteSpace(selectedPortalColorText))
		{
			selectedPortalColorText = MarkerColorValues[0];
		}
		selectedPortalColorIndex = GetMarkerColorIndex(selectedPortalColorText);
		selectedPortalMarkerText = GetDiscoveryMarkerText(portal);
		selectedPortalMarkerTextIndex = GetMarkerTextIndex(selectedPortalMarkerText);
		selectedPortalShowMarker = GetDiscoveryShowMarker(portal);
		selectedPortalSuppressGlow = GetDiscoverySuppressGlow(portal);
		selectedLockedHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryLockedHoverText");
		selectedRemoteHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryRemoteHoverText");
		selectedUnlockedHoverText = GetDiscoveryHoverOverride(portal, "PortalLimit_DiscoveryUnlockedHoverText");
		if (showStatus)
		{
			statusMessage = "Selected portal loaded. Choose what this portal does, then Apply.";
		}
	}

	private static void ApplyLocalPortalMarker(TeleportWorld portal, int role, string link, string markerColor, string markerText, bool showMarker, bool suppressGlow, string lockedHoverText, string remoteHoverText, string unlockedHoverText)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 != null)
		{
			val2.Set("PortalLimit_DiscoveryRole", role);
			val2.Set("PortalLimit_DiscoveryLink", (role == 0) ? string.Empty : link);
			val2.Set("PortalLimit_DiscoveryColor", (role == 0) ? string.Empty : markerColor);
			val2.Set("PortalLimit_DiscoveryMarkerText", (role == 0) ? string.Empty : NormalizeMarkerTextValue(markerText));
			val2.Set("PortalLimit_DiscoveryShowMarker", (role != 0) ? (showMarker ? 1 : 0) : 0);
			val2.Set("PortalLimit_DiscoverySuppressGlow", (role != 0) ? (suppressGlow ? 1 : 0) : 0);
			val2.Set("PortalLimit_DiscoveryLockedHoverText", (role == 0) ? string.Empty : (lockedHoverText ?? string.Empty).Trim());
			val2.Set("PortalLimit_DiscoveryRemoteHoverText", (role == 0) ? string.Empty : (remoteHoverText ?? string.Empty).Trim());
			val2.Set("PortalLimit_DiscoveryUnlockedHoverText", (role == 0) ? string.Empty : (unlockedHoverText ?? string.Empty).Trim());
		}
	}

	private int GetSelectedPortalRoleValue()
	{
		if (selectedPortalRole == 1)
		{
			return 1;
		}
		if (selectedPortalRole == 2)
		{
			return 2;
		}
		return 0;
	}

	private static string GetPortalRoleLabel(int role)
	{
		return role switch
		{
			1 => "Locked entrance", 
			2 => "Unlocked entrance", 
			_ => "Normal portal", 
		};
	}

	private static TeleportWorld GetHoveredPortal()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			return null;
		}
		GameObject hoverObject = ((Humanoid)Player.m_localPlayer).GetHoverObject();
		if ((Object)(object)hoverObject == (Object)null)
		{
			return null;
		}
		TeleportWorld val = hoverObject.GetComponent<TeleportWorld>();
		if ((Object)(object)val == (Object)null)
		{
			val = hoverObject.GetComponentInParent<TeleportWorld>();
		}
		return val;
	}

	private static string GetPortalKey(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? ((object)(ZDOID)(ref val2.m_uid)).ToString() : string.Empty;
	}

	private static string GetPortalCreatorName(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 == null)
		{
			return string.Empty;
		}
		string @string = val2.GetString(ZDOVars.s_creatorName, string.Empty);
		return @string ?? string.Empty;
	}

	private static bool CanEditPortalTag(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || (Object)(object)portal == (Object)null)
		{
			return false;
		}
		Piece val = ((Component)portal).GetComponent<Piece>();
		if ((Object)(object)val == (Object)null)
		{
			val = ((Component)portal).GetComponentInParent<Piece>();
		}
		if ((Object)(object)val == (Object)null)
		{
			return true;
		}
		long creator = val.GetCreator();
		long localPlayerId = GetLocalPlayerId();
		return creator == 0 || creator == localPlayerId;
	}

	private static string GetPortalTag(TeleportWorld portal)
	{
		MethodInfo methodInfo = AccessTools.Method(typeof(TeleportWorld), "GetText", (Type[])null, (Type[])null);
		if ((Object)(object)portal != (Object)null && methodInfo != null)
		{
			object obj = methodInfo.Invoke(portal, null);
			if (obj != null)
			{
				return obj.ToString();
			}
		}
		return string.Empty;
	}

	private static string GetPortalTagForUi(TeleportWorld portal)
	{
		string discoveryLink = GetDiscoveryLink(portal);
		if (!string.IsNullOrWhiteSpace(discoveryLink))
		{
			return discoveryLink;
		}
		return GetPortalTag(portal);
	}

	private static void SetPortalTag(TeleportWorld portal, string tag)
	{
		if ((Object)(object)portal == (Object)null)
		{
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(TeleportWorld), "SetText", new Type[1] { typeof(string) }, (Type[])null);
		if (methodInfo != null)
		{
			methodInfo.Invoke(portal, new object[1] { tag ?? string.Empty });
			return;
		}
		ZNetView component = ((Component)portal).GetComponent<ZNetView>();
		ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		if (val != null)
		{
			val.Set("tag", tag ?? string.Empty);
		}
	}

	private static void SetTargetFound(TeleportWorld portal, bool value)
	{
		if ((Object)(object)portal != (Object)null && TeleportWorldTargetFoundField != null)
		{
			TeleportWorldTargetFoundField.SetValue(portal, value);
		}
	}

	private static int GetDiscoveryRole(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetInt("PortalLimit_DiscoveryRole", 0) : 0;
	}

	private static string GetDiscoveryLink(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString("PortalLimit_DiscoveryLink", string.Empty) : string.Empty;
	}

	private static string GetDiscoveryColor(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString("PortalLimit_DiscoveryColor", string.Empty) : string.Empty;
	}

	private static string GetDiscoveryMarkerText(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? NormalizeMarkerTextValue(val2.GetString("PortalLimit_DiscoveryMarkerText", string.Empty)) : MarkerTextValues[0];
	}

	private bool GetDiscoveryShowMarker(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? (val2.GetInt("PortalLimit_DiscoveryShowMarker", discoveryShowWorldMarker ? 1 : 0) != 0) : discoveryShowWorldMarker;
	}

	private bool GetDiscoverySuppressGlow(TeleportWorld portal)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? (val2.GetInt("PortalLimit_DiscoverySuppressGlow", discoverySuppressLockedGlow ? 1 : 0) != 0) : discoverySuppressLockedGlow;
	}

	private static string GetDiscoveryHoverOverride(TeleportWorld portal, string key)
	{
		ZNetView val = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		return (val2 != null) ? val2.GetString(key, string.Empty) : string.Empty;
	}

	private Color GetDiscoveryVisualColor(TeleportWorld portal)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: 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_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: 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_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		string discoveryColor = GetDiscoveryColor(portal);
		if (!string.IsNullOrWhiteSpace(discoveryColor))
		{
			return ParseHexColor(discoveryColor, LockedOrange);
		}
		int discoveryRole = GetDiscoveryRole(portal);
		string discoveryLink = GetDiscoveryLink(portal);
		if (discoveryRole != 0 && !string.IsNullOrWhiteSpace(discoveryLink) && IsDiscoveryUnlocked(discoveryLink))
		{
			return ParseHexColor(discoveryUnlockedColor, Color.green);
		}
		if (discoveryRole == 2)
		{
			return ParseHexColor(discoveryRemoteColor, Color.cyan);
		}
		return ParseHexColor(discoveryLockedColor, LockedOrange);
	}

	private bool ShouldShowDiscoveryWorldMarker(TeleportWorld portal)
	{
		return discoveryEnabled && GetDiscoveryShowMarker(portal) && ShouldShowLockedEntranceX(portal);
	}

	private string GetDiscoveryMarkerDisplayText(TeleportWorld portal)
	{
		string discoveryMarkerText = GetDiscoveryMarkerText(portal);
		if (string.Equals(discoveryMarkerText, "Locked", StringComparison.OrdinalIgnoreCase))
		{
			return "LOCKED";
		}
		if (string.Equals(discoveryMarkerText, "X", StringComparison.OrdinalIgnoreCase))
		{
			return "X";
		}
		if (string.Equals(discoveryMarkerText, "UnlockOnOtherSide", StringComparison.OrdinalIgnoreCase))
		{
			return "UNLOCK ON OTHER SIDE";
		}
		return string.IsNullOrWhiteSpace(discoveryMarkerText) ? "UNLOCK ON OTHER SIDE" : discoveryMarkerText;
	}

	private bool ShouldShowLockedEntranceX(TeleportWorld portal)
	{
		if (GetDiscoveryRole(portal) != 1)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		return !string.IsNullOrWhiteSpace(discoveryLink) && !IsDiscoveryUnlocked(discoveryLink);
	}

	private bool ShouldTintDiscoveryPortal(TeleportWorld portal)
	{
		return false;
	}

	private bool ShouldSuppressLockedPortalGlow(TeleportWorld portal)
	{
		if (!discoveryEnabled || !GetDiscoverySuppressGlow(portal) || GetDiscoveryRole(portal) != 1)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		return !string.IsNullOrWhiteSpace(discoveryLink) && !IsDiscoveryUnlocked(discoveryLink);
	}

	private static Color ParseHexColor(string text, Color fallback)
	{
		//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_00c4: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrWhiteSpace(text))
		{
			return fallback;
		}
		string text2 = text.Trim();
		if (text2.StartsWith("#", StringComparison.Ordinal))
		{
			text2 = text2.Substring(1);
		}
		if (text2.Length == 6 && int.TryParse(text2.Substring(0, 2), NumberStyles.HexNumber, null, out var result) && int.TryParse(text2.Substring(2, 2), NumberStyles.HexNumber, null, out var result2) && int.TryParse(text2.Substring(4, 2), NumberStyles.HexNumber, null, out var result3))
		{
			return new Color((float)result / 255f, (float)result2 / 255f, (float)result3 / 255f, 1f);
		}
		return fallback;
	}

	private static string NormalizeHexColor(string text, string fallback)
	{
		string text2 = (text ?? string.Empty).Trim();
		if (!text2.StartsWith("#", StringComparison.Ordinal))
		{
			text2 = "#" + text2;
		}
		Color val = default(Color);
		return ColorUtility.TryParseHtmlString(text2, ref val) ? text2 : fallback;
	}

	private static bool TryNormalizeHexColor(string text, out string normalized)
	{
		normalized = (text ?? string.Empty).Trim();
		if (!normalized.StartsWith("#", StringComparison.Ordinal))
		{
			normalized = "#" + normalized;
		}
		Color val = default(Color);
		if (normalized.Length == 7 && ColorUtility.TryParseHtmlString(normalized, ref val))
		{
			return true;
		}
		normalized = string.Empty;
		return false;
	}

	private static int GetMarkerColorIndex(string color)
	{
		string a = NormalizeHexColor(color, MarkerColorValues[0]);
		for (int i = 0; i < MarkerColorValues.Length; i++)
		{
			if (string.Equals(a, MarkerColorValues[i], StringComparison.OrdinalIgnoreCase))
			{
				return i;
			}
		}
		return 0;
	}

	private static string NormalizeMarkerTextValue(string value)
	{
		string text = (value ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			return MarkerTextLabels[0];
		}
		for (int i = 0; i < MarkerTextValues.Length; i++)
		{
			if (string.Equals(text, MarkerTextValues[i], StringComparison.OrdinalIgnoreCase) || string.Equals(text, MarkerTextLabels[i], StringComparison.OrdinalIgnoreCase))
			{
				return MarkerTextLabels[i];
			}
		}
		return text;
	}

	private static int GetMarkerTextIndex(string value)
	{
		string a = NormalizeMarkerTextValue(value);
		for (int i = 0; i < MarkerTextValues.Length; i++)
		{
			if (string.Equals(a, MarkerTextValues[i], StringComparison.OrdinalIgnoreCase) || string.Equals(a, MarkerTextLabels[i], StringComparison.OrdinalIgnoreCase))
			{
				return i;
			}
		}
		return 0;
	}

	private bool IsDiscoveryUnlocked(string link)
	{
		if (string.IsNullOrWhiteSpace(link))
		{
			return false;
		}
		return unlockedDiscoveryLinks.Contains(link);
	}

	private void UnlockDiscoveryLink(string link, TeleportWorld portal)
	{
		//IL_005a: 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_0060: 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_0099: 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)
		if (!string.IsNullOrWhiteSpace(link))
		{
			unlockedDiscoveryLinks.Add(link);
			long localPlayerId = GetLocalPlayerId();
			if (localPlayerId != 0 && ZRoutedRpc.instance != null)
			{
				Vector3 val = (((Object)(object)portal != (Object)null) ? ((Component)portal).transform.position : Vector3.zero);
				ZNetView val2 = (((Object)(object)portal != (Object)null) ? ((Component)portal).GetComponent<ZNetView>() : null);
				ZDOID val3 = (((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO().m_uid : ZDOID.None);
				string text = "link=" + Escape(link.Trim()) + "\nplayerName=" + Escape(GetPlayerName()) + "\nportalId=" + Escape(((object)(ZDOID)(ref val3)).ToString()) + "\nx=" + val.x.ToString("F1") + "\ny=" + val.y.ToString("F1") + "\nz=" + val.z.ToString("F1");
				ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.discovery_unlock_request", new object[2] { localPlayerId, text });
			}
		}
	}

	private static bool ShouldBlockPortalUse(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || !Instance.discoveryEnabled || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return false;
		}
		int discoveryRole = GetDiscoveryRole(portal);
		if (discoveryRole == 0)
		{
			return false;
		}
		string discoveryLink = GetDiscoveryLink(portal);
		if (string.IsNullOrWhiteSpace(discoveryLink))
		{
			return false;
		}
		if (Instance.discoveryAdminBypass && Instance.isAdmin)
		{
			return false;
		}
		switch (discoveryRole)
		{
		case 2:
			if (!Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				Instance.UnlockDiscoveryLink(discoveryLink, portal);
				Instance.ShowMessage(Instance.discoveryUnlockMessage);
			}
			return false;
		case 1:
			if (!Instance.IsDiscoveryUnlocked(discoveryLink))
			{
				Instance.ShowMessage(Instance.discoveryLockedMessage);
				Instance.RequestUnlockList();
				return true;
			}
			break;
		}
		return false;
	}

	private static bool ShouldBlockBlankPortalUse(TeleportWorld portal)
	{
		if ((Object)(object)Instance == (Object)null || (Object)(object)portal == (Object)null)
		{
			return false;
		}
		if (string.IsNullOrWhiteSpace(GetPortalTag(portal)))
		{
			Instance.ShowMessage("Set a portal tag first.");
			return true;
		}
		return false;
	}

	internal static bool WouldExceedPortalLimit(Piece piece)
	{
		PortalLimitClientPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)piece == (Object)null || !instance.IsLimitedPortal(piece))
		{
			return false;
		}
		if (instance.maxPortalsPerPlayer <= 0)
		{
			return false;
		}
		if (instance.adminBypass && instance.isAdmin)
		{
			return false;
		}
		long localPlayerId = GetLocalPlayerId();
		int num = instance.CountPortals(localPlayerId);
		return num >= instance.maxPortalsPerPlayer;
	}

	private bool IsLimitedPortal(Piece piece)
	{
		if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponent<TeleportWorld>() == (Object)null)
		{
			return false;
		}
		HashSet<string> portalPrefabNameSet = GetPortalPrefabNameSet();
		if (portalPrefabNameSet.Count == 0)
		{
			return true;
		}
		ZNetView component = ((Component)piece).GetComponent<ZNetView>();
		string prefabName = GetPrefabName(component, piece);
		return portalPrefabNameSet.Contains(prefabName);
	}

	private void ReportPortalMetadata(Piece piece, string action)
	{
		//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)
		if (!((Object)(object)piece == (Object)null) && IsLimitedPortal(piece) && ZRoutedRpc.instance != null)
		{
			ZNetView component = ((Component)piece).GetComponent<ZNetView>();
			ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
			if (val != null)
			{
				Vector3 position = ((Component)piece).transform.position;
				string text = "action=" + Escape(action) + "\nportalId=" + Escape(((object)(ZDOID)(ref val.m_uid)).ToString()) + "\nplayerId=" + GetLocalPlayerId() + "\nplayerName=" + Escape(GetPlayerName()) + "\nx=" + position.x.ToString("F1") + "\ny=" + position.y.ToString("F1") + "\nz=" + position.z.ToString("F1");
				ZRoutedRpc.instance.InvokeRoutedRPC("keith.valheim.portallimit.portal_metadata_update", new object[1] { text });
			}
		}
	}

	private void ReportPortalMetadata(TeleportWorld portal, string action)
	{
		if (!((Object)(object)portal == (Object)null))
		{
			Piece val = ((Component)portal).GetComponent<Piece>();
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)portal).GetComponentInParent<Piece>();
			}
			ReportPortalMetadata(val, action);
		}
	}

	private static string GetPrefabName(ZNetView nview, Piece piece)
	{
		if ((Object)(object)nview != (Object)null && GetPrefabNameMethod != null)
		{
			object obj = GetPrefabNameMethod.Invoke(nview, null);
			if (obj != null)
			{
				return obj.ToString();
			}
		}
		return ((Object)(object)piece != (Object)null) ? ((Object)piece).name.Replace("(Clone)", string.Empty) : string.Empty;
	}

	private int CountPortals(long creatorId)
	{
		if (creatorId == 0 || AllPiecesField == null)
		{
			return serverPortalCount;
		}
		int num = 0;
		if (!(AllPiecesField.GetValue(null) is IEnumerable enumerable))
		{
			return serverPortalCount;
		}
		foreach (object item in enumerable)
		{
			Piece val = (Piece)((item is Piece) ? item : null);
			if ((Object)(object)val != (Object)null && val.GetCreator() == creatorId && IsLimitedPortal(val))
			{
				num++;
			}
		}
		return num;
	}

	private HashSet<string> GetPortalPrefabNameSet()
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		string[] array = portalPrefabNames.Split(new char[1] { ',' });
		foreach (string text in array)
		{
			string text2 = text.Trim();
			if (!string.IsNullOrWhiteSpace(text2))
			{
				hashSet.Add(text2);
			}
		}
		return hashSet;
	}

	private static long GetLocalPlayerId()
	{
		try
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null && GetPlayerIdMethod != null)
			{
				object obj = GetPlayerIdMethod.Invoke(Player.m_localPlayer, null);
				if (obj != null && obj.GetType() == typeof(long))
				{
					return (long)obj;
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not read local player ID: " + ex.Message));
		}
		return 0L;
	}

	private static string GetPlayerName()
	{
		return ((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "unknown-player";
	}

	internal void ShowMessage(string message)
	{
		if ((Object)(object)MessageHud.instance != (Object)null)
		{
			MessageHud.instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false);
		}
		else
		{
			Log.LogMessage((object)message);
		}
	}

	private static Dictionary<string, string> ParsePayload(string payload)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		if (string.IsNullOrEmpty(payload))
		{
			return dictionary;
		}
		string[] array = payload.Split(new char[1] { '\n' });
		foreach (string text in array)
		{
			int num = text.IndexOf('=');
			if (num > 0)
			{
				dictionary[text.Substring(0, num)] = Unescape(text.Substring(num + 1));
			}
		}
		return dictionary;
	}

	private static string Escape(string value)
	{
		return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\n", "\\n").Replace("=", "\\e");
	}

	private static string Unescape(string value)
	{
		return (value ?? string.Empty).Replace("\\e", "=").Replace("\\n", "\n").Replace("\\\\", "\\");
	}
}

BepInEx\plugins\PortalLimitServer.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace PortalLimit.Server;

[BepInPlugin("keith.valheim.portallimit.server", "Portal Limit and Discovery Server", "0.1.4")]
public class PortalLimitServerPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(TeleportWorld), "SetConnectedPortal")]
	private static class TeleportWorldSetConnectedPortalPatch
	{
		private static void Prefix(TeleportWorld __instance, ref ZDOID targetID)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				targetID = ZDOID.None;
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "RPC_SetConnected")]
	private static class TeleportWorldRpcSetConnectedPatch
	{
		private static void Prefix(TeleportWorld __instance, ref ZDOID portalID)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance)))
			{
				portalID = ZDOID.None;
			}
		}
	}

	[HarmonyPatch(typeof(Piece), "OnPlaced")]
	private static class PieceOnPlacedPatch
	{
		private static void Postfix(Piece __instance)
		{
			EnforceIfNeeded(__instance);
		}
	}

	[HarmonyPatch(typeof(WearNTear), "RPC_Remove")]
	private static class WearNTearRemovePatch
	{
		private static bool Prefix(WearNTear __instance, long sender)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)__instance == (Object)null)
			{
				return true;
			}
			Piece component = ((Component)__instance).GetComponent<Piece>();
			if (!Instance.IsLimitedPortal(component))
			{
				return true;
			}
			long creator = component.GetCreator();
			if (sender == creator || Instance.IsSenderAdmin(sender))
			{
				return true;
			}
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.notice", new object[1] { "Only the builder or an admin can remove this portal." });
			}
			Log.LogWarning((object)("Blocked portal remove request sender=" + sender + " creator=" + creator + "."));
			return false;
		}
	}

	public const string PluginGuid = "keith.valheim.portallimit.server";

	public const string PluginName = "Portal Limit and Discovery Server";

	public const string PluginVersion = "0.1.4";

	private const string RpcConfigRequest = "keith.valheim.portallimit.config_request";

	private const string RpcConfigResponse = "keith.valheim.portallimit.config_response";

	private const string RpcConfigUpdate = "keith.valheim.portallimit.config_update";

	private const string RpcCountRequest = "keith.valheim.portallimit.count_request";

	private const string RpcCountResponse = "keith.valheim.portallimit.count_response";

	private const string RpcNotice = "keith.valheim.portallimit.notice";

	private const string RpcDiscoveryMarkRequest = "keith.valheim.portallimit.discovery_mark_request";

	private const string RpcDiscoveryUnlockRequest = "keith.valheim.portallimit.discovery_unlock_request";

	private const string RpcDiscoveryRelockRequest = "keith.valheim.portallimit.discovery_relock_request";

	private const string RpcDiscoveryUnlockListRequest = "keith.valheim.portallimit.discovery_unlock_list_request";

	private const string RpcDiscoveryUnlockListResponse = "keith.valheim.portallimit.discovery_unlock_list_response";

	private const string RpcAdminReportRequest = "keith.valheim.portallimit.admin_report_request";

	private const string RpcAdminReportResponse = "keith.valheim.portallimit.admin_report_response";

	private const string RpcPortalMetadataUpdate = "keith.valheim.portallimit.portal_metadata_update";

	private const string DiscoveryRoleKey = "PortalLimit_DiscoveryRole";

	private const string DiscoveryLinkKey = "PortalLimit_DiscoveryLink";

	private const string DiscoveryColorKey = "PortalLimit_DiscoveryColor";

	private const string DiscoveryMarkerTextKey = "PortalLimit_DiscoveryMarkerText";

	private const string DiscoveryShowMarkerKey = "PortalLimit_DiscoveryShowMarker";

	private const string DiscoverySuppressGlowKey = "PortalLimit_DiscoverySuppressGlow";

	private const string DiscoveryLockedHoverTextKey = "PortalLimit_DiscoveryLockedHoverText";

	private const string DiscoveryRemoteHoverTextKey = "PortalLimit_DiscoveryRemoteHoverText";

	private const string DiscoveryUnlockedHoverTextKey = "PortalLimit_DiscoveryUnlockedHoverText";

	private const string PortalBuiltUtcKey = "PortalLimit_BuiltUtc";

	private const string PortalBuilderNameKey = "PortalLimit_BuilderName";

	private const string PortalBuilderIdKey = "PortalLimit_BuilderId";

	private const string PortalLastUsedUtcKey = "PortalLimit_LastUsedUtc";

	private const string PortalLastUsedNameKey = "PortalLimit_LastUsedName";

	private const string PortalLastUsedIdKey = "PortalLimit_LastUsedId";

	private const int DiscoveryRoleNone = 0;

	private const int DiscoveryRoleLocked = 1;

	private const int DiscoveryRoleRemote = 2;

	internal static PortalLimitServerPlugin Instance;

	internal static ManualLogSource Log;

	private static readonly FieldInfo AllPiecesField = AccessTools.Field(typeof(Piece), "s_allPieces");

	private static readonly FieldInfo PeerSocketField = AccessTools.Field(typeof(ZNetPeer), "m_socket");

	private static readonly FieldInfo OnZdoDestroyedField = AccessTools.Field(typeof(ZDOMan), "m_onZDODestroyed");

	private static readonly FieldInfo NetScenePrefabsField = AccessTools.Field(typeof(ZNetScene), "m_prefabs");

	private static readonly MethodInfo GetPrefabNameMethod = AccessTools.Method(typeof(ZNetView), "GetPrefabName", (Type[])null, (Type[])null);

	private static readonly MethodInfo GetPortalTextMethod = AccessTools.Method(typeof(TeleportWorld), "GetText", (Type[])null, (Type[])null);

	private Harmony harmony;

	private bool rpcRegistered;

	private bool portalIndexBuilt;

	private bool zdoDestroyedHooked;

	private string dataDirectory;

	private string unlockFilePath;

	private string unlockHistoryFilePath;

	private string portalCountsFilePath;

	private string portalPlacementLogFilePath;

	private readonly Dictionary<long, HashSet<string>> unlockedLinksByPlayer = new Dictionary<long, HashSet<string>>();

	private readonly Dictionary<long, HashSet<ZDOID>> portalIdsByCreator = new Dictionary<long, HashSet<ZDOID>>();

	private readonly Dictionary<ZDOID, long> creatorByPortalId = new Dictionary<ZDOID, long>();

	private readonly Dictionary<long, string> creatorNamesByCreator = new Dictionary<long, string>();

	private ConfigEntry<int> maxPortalsPerPlayer;

	private ConfigEntry<bool> adminBypass;

	private ConfigEntry<bool> enforceOnServer;

	private ConfigEntry<string> portalPrefabNames;

	private ConfigEntry<bool> discoveryPortalsEnabled;

	private ConfigEntry<bool> discoveryAdminBypass;

	private ConfigEntry<bool> adminHoldEToEdit;

	private ConfigEntry<bool> showDiscoveryHoverText;

	private ConfigEntry<string> discoveryLockedHoverText;

	private ConfigEntry<string> discoveryRemoteHoverText;

	private ConfigEntry<string> discoveryUnlockedHoverText;

	private ConfigEntry<string> discoveryLockedMessage;

	private ConfigEntry<string> discoveryUnlockMessage;

	private ConfigEntry<bool> discoveryShowWorldMarker;

	private ConfigEntry<bool> discoveryTintPortalModel;

	private ConfigEntry<bool> discoverySuppressLockedGlow;

	private ConfigEntry<string> discoveryLockedColor;

	private ConfigEntry<string> discoveryRemoteColor;

	private ConfigEntry<string> discoveryUnlockedColor;

	private void Awake()
	{
		//IL_033e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0348: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		maxPortalsPerPlayer = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxPortalsPerPlayer", 10, "Maximum number of matching portals each player may own. Set to 0 or below to disable the limit.");
		adminBypass = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AdminBypass", true, "Allow Valheim admins to exceed the portal limit.");
		enforceOnServer = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnforceOnServer", true, "Delete newly placed portals on the server when they exceed the limit. Keep this enabled for real enforcement.");
		portalPrefabNames = ((BaseUnityPlugin)this).Config.Bind<string>("General", "PortalPrefabNames", "portal_wood,portal_stone", "Comma-separated portal prefab names to limit. Leave empty to limit every piece with a TeleportWorld component.");
		discoveryPortalsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "Enabled", true, "Enable per-player discovery-gated portals.");
		discoveryAdminBypass = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "AdminBypass", true, "Allow Valheim admins to use locked discovery portals without unlocking them.");
		adminHoldEToEdit = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "AdminHoldEToEdit", true, "Allow admins with the client plugin to hold E on a portal to open the Portal Limit editor.");
		showDiscoveryHoverText = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "ShowHoverText", true, "Show discovery portal status lines in portal hover text for clients with the plugin.");
		discoveryLockedHoverText = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "LockedHoverText", "Locked: find the other end first.", "Hover text shown for locked discovery portals before a player unlocks the link.");
		discoveryRemoteHoverText = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "RemoteHoverText", "Discovery portal: use this side to unlock the route.", "Hover text shown for the discovery side of a locked route.");
		discoveryUnlockedHoverText = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "UnlockedHoverText", "Discovery route unlocked.", "Hover text shown after the player unlocks the route.");
		discoveryLockedMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "LockedMessage", "This portal is locked. Find and enter the other end first.", "Message shown when a player tries to use a locked discovery portal.");
		discoveryUnlockMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "UnlockMessage", "Discovery portal unlocked.", "Message shown when a player unlocks a discovery portal route.");
		discoveryShowWorldMarker = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "ShowWorldMarker", true, "Show a visible marker on marked discovery portals for clients with the plugin.");
		discoveryTintPortalModel = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "TintPortalModel", false, "Tint the portal model color for clients with the plugin.");
		discoverySuppressLockedGlow = ((BaseUnityPlugin)this).Config.Bind<bool>("Discovery Portals", "SuppressLockedGlow", false, "Hide connected portal glow and particle effects on locked portals before the route is unlocked.");
		discoveryLockedColor = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "LockedColor", "#ff9f1a", "Hex color for locked portals before the route is unlocked.");
		discoveryRemoteColor = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "RemoteColor", "#38a6ff", "Hex color for the far-side portal that unlocks the route.");
		discoveryUnlockedColor = ((BaseUnityPlugin)this).Config.Bind<string>("Discovery Portals", "UnlockedColor", "#42ff68", "Hex color for portals after the player has unlocked the route.");
		dataDirectory = Path.Combine(Paths.ConfigPath, "PortalLimit");
		unlockFilePath = Path.Combine(dataDirectory, "discovery_unlocks.txt");
		unlockHistoryFilePath = Path.Combine(dataDirectory, "discovery_unlock_history.tsv");
		portalCountsFilePath = Path.Combine(dataDirectory, "portal_counts.tsv");
		portalPlacementLogFilePath = Path.Combine(dataDirectory, "portal_placements.tsv");
		Directory.CreateDirectory(dataDirectory);
		EnsurePlacementLogHeader();
		EnsureUnlockHistoryHeader();
		LoadUnlocks();
		harmony = new Harmony("keith.valheim.portallimit.server");
		harmony.PatchAll();
		((MonoBehaviour)this).InvokeRepeating("TryRegisterRpcs", 1f, 1f);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Portal Limit server component loaded for dedicated or local-host server use.");
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		UnhookZdoDestroyed();
	}

	private static bool IsDedicatedServerProcess()
	{
		string processName = Process.GetCurrentProcess().ProcessName;
		return processName.StartsWith("valheim_server", StringComparison.OrdinalIgnoreCase);
	}

	private void TryRegisterRpcs()
	{
		if (!rpcRegistered && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.config_request", (Action<long, string>)OnConfigRequest);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.config_update", (Action<long, string>)OnConfigUpdate);
			ZRoutedRpc.instance.Register<long>("keith.valheim.portallimit.count_request", (Action<long, long>)OnCountRequest);
			ZRoutedRpc.instance.Register<ZDOID, string>("keith.valheim.portallimit.discovery_mark_request", (Action<long, ZDOID, string>)OnDiscoveryMarkRequest);
			ZRoutedRpc.instance.Register<long, string>("keith.valheim.portallimit.discovery_unlock_request", (Action<long, long, string>)OnDiscoveryUnlockRequest);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.discovery_relock_request", (Action<long, string>)OnDiscoveryRelockRequest);
			ZRoutedRpc.instance.Register<long>("keith.valheim.portallimit.discovery_unlock_list_request", (Action<long, long>)OnDiscoveryUnlockListRequest);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.admin_report_request", (Action<long, string>)OnAdminReportRequest);
			ZRoutedRpc.instance.Register<string>("keith.valheim.portallimit.portal_metadata_update", (Action<long, string>)OnPortalMetadataUpdate);
			rpcRegistered = true;
			((MonoBehaviour)this).CancelInvoke("TryRegisterRpcs");
			HookZdoDestroyed();
			Log.LogInfo((object)"Registered portal limit server RPC handlers.");
		}
	}

	private void OnConfigRequest(long sender, string playerName)
	{
		if (IsDedicatedOrHostServer())
		{
			bool flag = false;
			if (!string.IsNullOrEmpty(playerName) && playerName.StartsWith("[silent]", StringComparison.Ordinal))
			{
				flag = true;
				playerName = playerName.Substring("[silent]".Length);
			}
			bool flag2 = IsSenderAdmin(sender);
			string message = (flag ? string.Empty : (flag2 ? "Portal limit settings loaded." : "Only admins can change portal limit settings."));
			string text = BuildConfigPayload(flag2, message);
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { text });
			Log.LogInfo((object)("Config request from " + playerName + " admin=" + flag2));
		}
	}

	private void OnConfigUpdate(long sender, string payload)
	{
		if (!IsDedicatedOrHostServer())
		{
			return;
		}
		if (!IsSenderAdmin(sender))
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can change portal limit settings.") });
			return;
		}
		Dictionary<string, string> dictionary = ParsePayload(payload);
		if (dictionary.TryGetValue("max", out var value) && int.TryParse(value, out var result))
		{
			maxPortalsPerPlayer.Value = Math.Max(0, result);
		}
		if (dictionary.TryGetValue("adminBypass", out value) && bool.TryParse(value, out var result2))
		{
			adminBypass.Value = result2;
		}
		if (dictionary.TryGetValue("enforceOnServer", out value) && bool.TryParse(value, out result2))
		{
			enforceOnServer.Value = result2;
		}
		if (dictionary.TryGetValue("portalPrefabNames", out value))
		{
			portalPrefabNames.Value = value.Trim();
			portalIndexBuilt = false;
		}
		if (dictionary.TryGetValue("discoveryEnabled", out value) && bool.TryParse(value, out result2))
		{
			discoveryPortalsEnabled.Value = result2;
		}
		if (dictionary.TryGetValue("discoveryAdminBypass", out value) && bool.TryParse(value, out result2))
		{
			discoveryAdminBypass.Value = result2;
		}
		if (dictionary.TryGetValue("adminHoldEToEdit", out value) && bool.TryParse(value, out result2))
		{
			adminHoldEToEdit.Value = result2;
		}
		if (dictionary.TryGetValue("showDiscoveryHoverText", out value) && bool.TryParse(value, out result2))
		{
			showDiscoveryHoverText.Value = result2;
		}
		if (dictionary.TryGetValue("discoveryLockedHoverText", out value))
		{
			discoveryLockedHoverText.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryRemoteHoverText", out value))
		{
			discoveryRemoteHoverText.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryUnlockedHoverText", out value))
		{
			discoveryUnlockedHoverText.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryLockedMessage", out value))
		{
			discoveryLockedMessage.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryUnlockMessage", out value))
		{
			discoveryUnlockMessage.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryShowWorldMarker", out value) && bool.TryParse(value, out result2))
		{
			discoveryShowWorldMarker.Value = result2;
		}
		if (dictionary.TryGetValue("discoveryTintPortalModel", out value) && bool.TryParse(value, out result2))
		{
			discoveryTintPortalModel.Value = result2;
		}
		if (dictionary.TryGetValue("discoverySuppressLockedGlow", out value) && bool.TryParse(value, out result2))
		{
			discoverySuppressLockedGlow.Value = result2;
		}
		if (dictionary.TryGetValue("discoveryLockedColor", out value))
		{
			discoveryLockedColor.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryRemoteColor", out value))
		{
			discoveryRemoteColor.Value = value.Trim();
		}
		if (dictionary.TryGetValue("discoveryUnlockedColor", out value))
		{
			discoveryUnlockedColor.Value = value.Trim();
		}
		((BaseUnityPlugin)this).Config.Save();
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Portal limit settings saved.") });
		Log.LogInfo((object)("Portal limit config updated by peer " + sender + "."));
	}

	private void OnCountRequest(long sender, long creatorId)
	{
		if (IsDedicatedOrHostServer())
		{
			EnsurePortalIndexBuilt();
			bool flag = IsSenderAdmin(sender);
			long creatorId2 = (flag ? creatorId : sender);
			int num = CountPortals(creatorId2);
			int value = maxPortalsPerPlayer.Value;
			bool flag2 = value > 0 && num >= value;
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.count_response", new object[4]
			{
				num,
				value,
				flag2,
				BuildConfigPayload(flag, string.Empty)
			});
		}
	}

	private void OnAdminReportRequest(long sender, string command)
	{
		if (IsDedicatedOrHostServer())
		{
			if (!IsSenderAdmin(sender))
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.admin_report_response", new object[1] { "Portal Limit: only admins can use server-wide F5 reports." });
			}
			else
			{
				string text = BuildAdminReport(command);
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.admin_report_response", new object[1] { text });
			}
		}
	}

	private void OnDiscoveryMarkRequest(long sender, ZDOID portalId, string payload)
	{
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0396: Unknown result type (might be due to invalid IL or missing references)
		//IL_03aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_04fa: Unknown result type (might be due to invalid IL or missing references)
		if (!IsDedicatedOrHostServer())
		{
			return;
		}
		if (!IsSenderAdmin(sender))
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can mark discovery portals.") });
			return;
		}
		if (ZDOMan.instance == null || ((ZDOID)(ref portalId)).IsNone())
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "No valid portal was targeted.") });
			return;
		}
		ZDO zDO = ZDOMan.instance.GetZDO(portalId);
		if (zDO == null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Could not find that portal on the server.") });
			return;
		}
		Dictionary<string, string> dictionary = ParsePayload(payload);
		int.TryParse(dictionary.TryGetValue("role", out var value) ? value : "0", out var result);
		bool.TryParse(dictionary.TryGetValue("showMarker", out value) ? value : discoveryShowWorldMarker.Value.ToString(), out var result2);
		bool.TryParse(dictionary.TryGetValue("suppressGlow", out value) ? value : discoverySuppressLockedGlow.Value.ToString(), out var result3);
		int num = ((result == 1 || result == 2) ? result : 0);
		string text = (dictionary.TryGetValue("link", out value) ? value.Trim() : string.Empty);
		string normalized = (dictionary.TryGetValue("markerColor", out value) ? value.Trim() : string.Empty);
		string text2 = NormalizeMarkerTextValue(dictionary.TryGetValue("markerText", out value) ? value : string.Empty);
		string text3 = (dictionary.TryGetValue("lockedHoverText", out value) ? value.Trim() : string.Empty);
		string text4 = (dictionary.TryGetValue("remoteHoverText", out value) ? value.Trim() : string.Empty);
		string text5 = (dictionary.TryGetValue("unlockedHoverText", out value) ? value.Trim() : string.Empty);
		if (num != 0 && string.IsNullOrWhiteSpace(text))
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a portal tag first.") });
			return;
		}
		if (num != 0)
		{
			if (!TryNormalizeHexColor(normalized, out normalized))
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a marker color like #ff3030.") });
				return;
			}
			if (TryFindDiscoveryEndpointConflict(portalId, num, text, out var conflictId))
			{
				string text6 = ((num == 1) ? "locked entrance" : "unlocked entrance");
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "A " + text6 + " already exists for portal tag '" + text + "'.") });
				Log.LogWarning((object)string.Concat("Rejected duplicate discovery ", text6, " for link '", text, "' existing=", conflictId, " attempted=", portalId));
				return;
			}
		}
		zDO.Set("PortalLimit_DiscoveryRole", num);
		zDO.Set("PortalLimit_DiscoveryLink", (num == 0) ? string.Empty : text);
		zDO.Set("PortalLimit_DiscoveryColor", (num == 0) ? string.Empty : normalized);
		zDO.Set("PortalLimit_DiscoveryMarkerText", (num == 0) ? string.Empty : text2);
		zDO.Set("PortalLimit_DiscoveryShowMarker", (num != 0) ? (result2 ? 1 : 0) : 0);
		zDO.Set("PortalLimit_DiscoverySuppressGlow", (num != 0) ? (result3 ? 1 : 0) : 0);
		zDO.Set("PortalLimit_DiscoveryLockedHoverText", (num == 0) ? string.Empty : text3);
		zDO.Set("PortalLimit_DiscoveryRemoteHoverText", (num == 0) ? string.Empty : text4);
		zDO.Set("PortalLimit_DiscoveryUnlockedHoverText", (num == 0) ? string.Empty : text5);
		ZDOMan.instance.ForceSendZDO(portalId);
		string text7 = ((num == 0) ? "Portal settings saved." : "Portal settings saved.");
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, text7) });
		Log.LogInfo((object)(text7 + " zdo=" + portalId));
	}

	private void OnDiscoveryRelockRequest(long sender, string link)
	{
		if (!IsDedicatedOrHostServer())
		{
			return;
		}
		if (!IsSenderAdmin(sender))
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can relock discovery portals.") });
			return;
		}
		string text = (link ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a portal tag first.") });
			return;
		}
		int num = 0;
		foreach (HashSet<string> value in unlockedLinksByPlayer.Values)
		{
			if (value.Remove(text))
			{
				num++;
			}
		}
		SaveUnlocks();
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Relocked portal tag '" + text + "' for " + num + " player record(s).") });
		Log.LogInfo((object)("Relocked discovery portal link '" + text + "' by admin sender " + sender + "."));
	}

	private bool TryFindDiscoveryEndpointConflict(ZDOID currentPortalId, int role, string link, out ZDOID conflictId)
	{
		//IL_0003: 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_0050: 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)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		conflictId = ZDOID.None;
		if (ZDOMan.instance == null || string.IsNullOrWhiteSpace(link))
		{
			return false;
		}
		EnsurePortalIndexBuilt();
		foreach (ZDOID item in new List<ZDOID>(creatorByPortalId.Keys))
		{
			ZDO zDO = ZDOMan.instance.GetZDO(item);
			if (zDO == null || ((ZDOID)(ref zDO.m_uid)).Equals(currentPortalId) || zDO.GetInt("PortalLimit_DiscoveryRole", 0) != role || !string.Equals(zDO.GetString("PortalLimit_DiscoveryLink", string.Empty), link, StringComparison.OrdinalIgnoreCase))
			{
				continue;
			}
			conflictId = zDO.m_uid;
			return true;
		}
		return false;
	}

	private void OnDiscoveryUnlockRequest(long sender, long playerId, string link)
	{
		if (!IsDedicatedOrHostServer())
		{
			return;
		}
		long num = (IsSenderAdmin(sender) ? playerId : sender);
		Dictionary<string, string> dictionary = ParsePayload(link);
		string value;
		string text = ((dictionary.Count > 0 && dictionary.TryGetValue("link", out value)) ? value.Trim() : (link ?? string.Empty).Trim());
		string playerName = (dictionary.TryGetValue("playerName", out value) ? value.Trim() : string.Empty);
		string portalId = (dictionary.TryGetValue("portalId", out value) ? value.Trim() : string.Empty);
		string x = (dictionary.TryGetValue("x", out value) ? value.Trim() : string.Empty);
		string y = (dictionary.TryGetValue("y", out value) ? value.Trim() : string.Empty);
		string z = (dictionary.TryGetValue("z", out value) ? value.Trim() : string.Empty);
		if (discoveryPortalsEnabled.Value && num != 0 && !string.IsNullOrWhiteSpace(text))
		{
			HashSet<string> orCreateUnlockSet = GetOrCreateUnlockSet(num);
			bool flag = orCreateUnlockSet.Add(text);
			if (flag)
			{
				SaveUnlocks();
				AppendUnlockHistoryLog(num, playerName, text, portalId, x, y, z);
				Log.LogInfo((object)("Unlocked discovery portal link '" + text + "' for player " + num + "."));
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.discovery_unlock_list_response", new object[1] { BuildUnlockPayload(num, flag ? discoveryUnlockMessage.Value : string.Empty) });
		}
	}

	private void OnDiscoveryUnlockListRequest(long sender, long playerId)
	{
		if (IsDedicatedOrHostServer())
		{
			long playerId2 = (IsSenderAdmin(sender) ? playerId : sender);
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.discovery_unlock_list_response", new object[1] { BuildUnlockPayload(playerId2, string.Empty) });
		}
	}

	private void OnPortalMetadataUpdate(long sender, string payload)
	{
		//IL_0269: Unknown result type (might be due to invalid IL or missing references)
		if (!IsDedicatedOrHostServer())
		{
			return;
		}
		Dictionary<string, string> dictionary = ParsePayload(payload);
		string value;
		string a = (dictionary.TryGetValue("action", out value) ? value.Trim() : string.Empty);
		string portalKey = (dictionary.TryGetValue("portalId", out value) ? value.Trim() : string.Empty);
		string text = (dictionary.TryGetValue("playerName", out value) ? value.Trim() : string.Empty);
		if (!dictionary.TryGetValue("playerId", out value) || !long.TryParse(value, out var result))
		{
			result = sender;
		}
		if (result == 0)
		{
			result = sender;
		}
		ZDO val = FindPortalZdoByKey(portalKey);
		if (val == null)
		{
			return;
		}
		bool flag = IsSenderAdmin(sender);
		long @long = val.GetLong(ZDOVars.s_creator, 0L);
		bool flag2 = @long == 0 || @long == sender || @long == result;
		if (!flag && !flag2 && !string.Equals(a, "used", StringComparison.OrdinalIgnoreCase))
		{
			return;
		}
		if (!string.IsNullOrWhiteSpace(text))
		{
			creatorNamesByCreator[result] = text;
		}
		if (string.Equals(a, "placed", StringComparison.OrdinalIgnoreCase))
		{
			if (string.IsNullOrWhiteSpace(val.GetString("PortalLimit_BuiltUtc", string.Empty)))
			{
				val.Set("PortalLimit_BuiltUtc", DateTime.UtcNow.ToString("o"));
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				val.Set("PortalLimit_BuilderName", text);
			}
			if (result != 0)
			{
				val.Set("PortalLimit_BuilderId", result);
				TrackPortal(val, result);
			}
		}
		else if (string.Equals(a, "used", StringComparison.OrdinalIgnoreCase))
		{
			val.Set("PortalLimit_LastUsedUtc", DateTime.UtcNow.ToString("o"));
			if (!string.IsNullOrWhiteSpace(text))
			{
				val.Set("PortalLimit_LastUsedName", text);
			}
			if (result != 0)
			{
				val.Set("PortalLimit_LastUsedId", result);
			}
		}
		if (ZDOMan.instance != null)
		{
			ZDOMan.instance.ForceSendZDO(val.m_uid);
		}
	}

	internal static void EnforceIfNeeded(Piece piece)
	{
		if ((Object)(object)Instance == (Object)null || !Instance.enforceOnServer.Value || !IsDedicatedOrHostServer() || !Instance.IsLimitedPortal(piece))
		{
			return;
		}
		long creator = piece.GetCreator();
		if (creator == 0)
		{
			((MonoBehaviour)Instance).StartCoroutine(Instance.EnforceNextFrame(piece));
			return;
		}
		Instance.EnsurePortalIndexBuilt();
		Instance.TrackPortal(piece, creator);
		int num = Instance.CountPortals(creator);
		int value = Instance.maxPortalsPerPlayer.Value;
		if (Instance.adminBypass.Value && Instance.IsCreatorAdmin(creator))
		{
			Instance.SavePortalCounts();
			Instance.AppendPortalPlacementLog(piece, creator, "PLACED_ADMIN_BYPASS");
			Instance.SendPortalCountNotice(creator, num, value);
		}
		else if (value <= 0)
		{
			Instance.SavePortalCounts();
			Instance.AppendPortalPlacementLog(piece, creator, "PLACED");
			Instance.SendPortalCountNotice(creator, num, value);
		}
		else if (num <= Instance.maxPortalsPerPlayer.Value)
		{
			Instance.SavePortalCounts();
			Instance.AppendPortalPlacementLog(piece, creator, "PLACED");
			Instance.SendPortalCountNotice(creator, num, value);
		}
		else
		{
			((MonoBehaviour)Instance).StartCoroutine(Instance.RemoveExcessPortalNextFrame(piece, creator, num));
		}
	}

	private IEnumerator EnforceNextFrame(Piece piece)
	{
		yield return null;
		if (!((Object)(object)piece == (Object)null))
		{
			long creatorId = piece.GetCreator();
			if (creatorId == 0)
			{
				Log.LogWarning((object)"Could not enforce portal limit because placed portal has no creator id.");
			}
			else
			{
				EnforceIfNeeded(piece);
			}
		}
	}

	private IEnumerator RemoveExcessPortalNextFrame(Piece piece, long creatorId, int count)
	{
		yield return null;
		if (!((Object)(object)piece == (Object)null))
		{
			Log.LogInfo((object)("Removing portal from creator " + creatorId + " because they have " + count + "/" + maxPortalsPerPlayer.Value + "."));
			AppendPortalPlacementLog(piece, creatorId, "REMOVED_OVER_LIMIT");
			UntrackPortal(piece);
			RemovePiece(piece);
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(creatorId, "keith.valheim.portallimit.notice", new object[1] { "Portal limit reached: " + maxPortalsPerPlayer.Value + " portal(s) per player." });
			}
			SavePortalCounts();
		}
	}

	private void SendPortalCountNotice(long creatorId, int count, int max)
	{
		if (ZRoutedRpc.instance != null && creatorId != 0)
		{
			string text = ((max > 0) ? max.ToString() : "unlimited");
			ZRoutedRpc.instance.InvokeRoutedRPC(creatorId, "keith.valheim.portallimit.notice", new object[1] { "Portals placed: " + count + " / " + text });
		}
	}

	private void RemovePiece(Piece piece)
	{
		WearNTear component = ((Component)piece).GetComponent<WearNTear>();
		if ((Object)(object)component != (Object)null)
		{
			component.Remove(true);
			return;
		}
		ZNetView component2 = ((Component)piece).GetComponent<ZNetView>();
		if ((Object)(object)component2 != (Object)null && component2.IsValid())
		{
			component2.Destroy();
		}
	}

	private bool IsLimitedPortal(Piece piece)
	{
		if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponent<TeleportWorld>() == (Object)null)
		{
			return false;
		}
		HashSet<string> portalPrefabNameSet = GetPortalPrefabNameSet();
		if (portalPrefabNameSet.Count == 0)
		{
			return true;
		}
		ZNetView component = ((Component)piece).GetComponent<ZNetView>();
		string prefabName = GetPrefabName(component, piece);
		return portalPrefabNameSet.Contains(prefabName);
	}

	private static string GetPrefabName(ZNetView nview, Piece piece)
	{
		if ((Object)(object)nview != (Object)null && GetPrefabNameMethod != null)
		{
			object obj = GetPrefabNameMethod.Invoke(nview, null);
			if (obj != null)
			{
				return obj.ToString();
			}
		}
		return ((Object)(object)piece != (Object)null) ? ((Object)piece).name.Replace("(Clone)", string.Empty) : string.Empty;
	}

	private int CountPortals(long creatorId)
	{
		if (creatorId == 0)
		{
			return 0;
		}
		EnsurePortalIndexBuilt();
		HashSet<ZDOID> value;
		return portalIdsByCreator.TryGetValue(creatorId, out value) ? value.Count : 0;
	}

	private void EnsurePortalIndexBuilt()
	{
		if (!portalIndexBuilt && ZDOMan.instance != null)
		{
			RebuildPortalIndex();
		}
	}

	private void RebuildPortalIndex()
	{
		portalIdsByCreator.Clear();
		creatorByPortalId.Clear();
		creatorNamesByCreator.Clear();
		if (ZDOMan.instance == null)
		{
			return;
		}
		HashSet<string> strictPortalPrefabNameSet = GetStrictPortalPrefabNameSet();
		foreach (string item in strictPortalPrefabNameSet)
		{
			int num = 0;
			List<ZDO> list = new List<ZDO>();
			while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(item, list, ref num))
			{
			}
			foreach (ZDO item2 in list)
			{
				TrackPortal(item2);
			}
		}
		portalIndexBuilt = true;
		SavePortalCounts();
		Log.LogInfo((object)("Built global portal index with " + creatorByPortalId.Count + " portal(s) across " + portalIdsByCreator.Count + " creator(s)."));
	}

	private HashSet<string> GetStrictPortalPrefabNameSet()
	{
		HashSet<string> portalPrefabNameSet = GetPortalPrefabNameSet();
		if (portalPrefabNameSet.Count > 0)
		{
			return portalPrefabNameSet;
		}
		if ((Object)(object)ZNetScene.instance == (Object)null || NetScenePrefabsField == null)
		{
			return portalPrefabNameSet;
		}
		if (!(NetScenePrefabsField.GetValue(ZNetScene.instance) is IEnumerable enumerable))
		{
			return portalPrefabNameSet;
		}
		foreach (object item in enumerable)
		{
			GameObject val = (GameObject)((item is GameObject) ? item : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<TeleportWorld>() != (Object)null)
			{
				portalPrefabNameSet.Add(((Object)val).name.Replace("(Clone)", string.Empty));
			}
		}
		return portalPrefabNameSet;
	}

	private void TrackPortal(Piece piece, long creatorId)
	{
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 != null && string.IsNullOrWhiteSpace(val2.GetString("PortalLimit_BuiltUtc", string.Empty)))
		{
			val2.Set("PortalLimit_BuiltUtc", DateTime.UtcNow.ToString("o"));
			if (ZDOMan.instance != null)
			{
				ZDOMan.instance.ForceSendZDO(val2.m_uid);
			}
		}
		TrackPortal(val2, creatorId);
	}

	private void TrackPortal(ZDO zdo)
	{
		TrackPortal(zdo, (zdo != null) ? zdo.GetLong(ZDOVars.s_creator, 0L) : 0);
	}

	private void TrackPortal(ZDO zdo, long creatorId)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: 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_0077: 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)
		if (zdo != null && creatorId != 0)
		{
			ZDOID uid = zdo.m_uid;
			string @string = zdo.GetString("PortalLimit_BuilderName", string.Empty);
			if (string.IsNullOrWhiteSpace(@string))
			{
				@string = zdo.GetString(ZDOVars.s_creatorName, string.Empty);
			}
			if (creatorByPortalId.TryGetValue(uid, out var value) && value != creatorId)
			{
				RemovePortalIdFromCreator(value, uid);
			}
			creatorByPortalId[uid] = creatorId;
			if (!string.IsNullOrWhiteSpace(@string))
			{
				creatorNamesByCreator[creatorId] = @string;
			}
			if (!portalIdsByCreator.TryGetValue(creatorId, out var value2))
			{
				value2 = new HashSet<ZDOID>();
				portalIdsByCreator[creatorId] = value2;
			}
			value2.Add(uid);
		}
	}

	private void UntrackPortal(Piece piece)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 != null)
		{
			UntrackPortal(val2.m_uid);
		}
	}

	private void UntrackPortal(ZDOID id)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		if (creatorByPortalId.TryGetValue(id, out var value))
		{
			creatorByPortalId.Remove(id);
			RemovePortalIdFromCreator(value, id);
		}
	}

	private void RemovePortalIdFromCreator(long creatorId, ZDOID id)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		if (portalIdsByCreator.TryGetValue(creatorId, out var value))
		{
			value.Remove(id);
			if (value.Count == 0)
			{
				portalIdsByCreator.Remove(creatorId);
				creatorNamesByCreator.Remove(creatorId);
			}
		}
	}

	private void HookZdoDestroyed()
	{
		if (!zdoDestroyedHooked && ZDOMan.instance != null && !(OnZdoDestroyedField == null))
		{
			Action<ZDO> a = OnZdoDestroyedField.GetValue(ZDOMan.instance) as Action<ZDO>;
			a = (Action<ZDO>)Delegate.Combine(a, new Action<ZDO>(OnZdoDestroyed));
			OnZdoDestroyedField.SetValue(ZDOMan.instance, a);
			zdoDestroyedHooked = true;
		}
	}

	private void UnhookZdoDestroyed()
	{
		if (zdoDestroyedHooked && ZDOMan.instance != null && !(OnZdoDestroyedField == null))
		{
			Action<ZDO> source = OnZdoDestroyedField.GetValue(ZDOMan.instance) as Action<ZDO>;
			source = (Action<ZDO>)Delegate.Remove(source, new Action<ZDO>(OnZdoDestroyed));
			OnZdoDestroyedField.SetValue(ZDOMan.instance, source);
			zdoDestroyedHooked = false;
		}
	}

	private void OnZdoDestroyed(ZDO zdo)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		if (zdo != null)
		{
			UntrackPortal(zdo.m_uid);
			SavePortalCounts();
		}
	}

	private HashSet<string> GetPortalPrefabNameSet()
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		string[] array = portalPrefabNames.Value.Split(new char[1] { ',' });
		foreach (string text in array)
		{
			string text2 = text.Trim();
			if (!string.IsNullOrWhiteSpace(text2))
			{
				hashSet.Add(text2);
			}
		}
		return hashSet;
	}

	private bool IsCreatorAdmin(long creatorId)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		ZNetPeer peer = instance.GetPeer(creatorId);
		if (peer != null)
		{
			return IsPeerAdmin(peer);
		}
		return instance.IsServer() && !IsDedicatedServerProcess();
	}

	private bool IsSenderAdmin(long sender)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		ZNetPeer peer = instance.GetPeer(sender);
		if (peer != null)
		{
			return IsPeerAdmin(peer);
		}
		return instance.IsServer() && !IsDedicatedServerProcess();
	}

	private bool IsPeerAdmin(ZNetPeer peer)
	{
		try
		{
			ISocket val = (ISocket)((PeerSocketField != null) ? /*isinst with value type is only supported in some contexts*/: null);
			string text = ((val != null) ? val.GetHostName() : string.Empty);
			return !string.IsNullOrWhiteSpace(text) && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsAdmin(text);
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not check admin status: " + ex.Message));
			return false;
		}
	}

	private string BuildConfigPayload(bool isAdmin, string message)
	{
		return "isAdmin=" + isAdmin + "\nmax=" + maxPortalsPerPlayer.Value + "\nadminBypass=" + adminBypass.Value + "\nenforceOnServer=" + enforceOnServer.Value + "\nportalPrefabNames=" + Escape(portalPrefabNames.Value) + "\ndiscoveryEnabled=" + discoveryPortalsEnabled.Value + "\ndiscoveryAdminBypass=" + discoveryAdminBypass.Value + "\nadminHoldEToEdit=" + adminHoldEToEdit.Value + "\nshowDiscoveryHoverText=" + showDiscoveryHoverText.Value + "\ndiscoveryLockedHoverText=" + Escape(discoveryLockedHoverText.Value) + "\ndiscoveryRemoteHoverText=" + Escape(discoveryRemoteHoverText.Value) + "\ndiscoveryUnlockedHoverText=" + Escape(discoveryUnlockedHoverText.Value) + "\ndiscoveryLockedMessage=" + Escape(discoveryLockedMessage.Value) + "\ndiscoveryUnlockMessage=" + Escape(discoveryUnlockMessage.Value) + "\ndiscoveryShowWorldMarker=" + discoveryShowWorldMarker.Value + "\ndiscoveryTintPortalModel=" + discoveryTintPortalModel.Value + "\ndiscoverySuppressLockedGlow=" + discoverySuppressLockedGlow.Value + "\ndiscoveryLockedColor=" + Escape(discoveryLockedColor.Value) + "\ndiscoveryRemoteColor=" + Escape(discoveryRemoteColor.Value) + "\ndiscoveryUnlockedColor=" + Escape(discoveryUnlockedColor.Value) + "\nmessage=" + Escape(message);
	}

	private string BuildUnlockPayload(long playerId, string message)
	{
		HashSet<string> orCreateUnlockSet = GetOrCreateUnlockSet(playerId);
		return "links=" + Escape(string.Join(";", new List<string>(orCreateUnlockSet).ToArray())) + "\nmessage=" + Escape(message);
	}

	private HashSet<string> GetOrCreateUnlockSet(long playerId)
	{
		if (!unlockedLinksByPlayer.TryGetValue(playerId, out var value))
		{
			value = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			unlockedLinksByPlayer[playerId] = value;
		}
		return value;
	}

	private void LoadUnlocks()
	{
		unlockedLinksByPlayer.Clear();
		if (!File.Exists(unlockFilePath))
		{
			return;
		}
		string[] array = File.ReadAllLines(unlockFilePath);
		foreach (string text in array)
		{
			if (string.IsNullOrWhiteSpace(text) || text.TrimStart(new char[0]).StartsWith("#", StringComparison.Ordinal))
			{
				continue;
			}
			string[] array2 = text.Split(new char[1] { '|' });
			if (array2.Length >= 2 && long.TryParse(array2[0].Trim(), out var result))
			{
				string text2 = array2[1].Trim();
				if (!string.IsNullOrWhiteSpace(text2))
				{
					GetOrCreateUnlockSet(result).Add(text2);
				}
			}
		}
	}

	private void SaveUnlocks()
	{
		List<string> list = new List<string>();
		list.Add("# player id | discovery link id");
		foreach (KeyValuePair<long, HashSet<string>> item in unlockedLinksByPlayer)
		{
			foreach (string item2 in item.Value)
			{
				list.Add(item.Key + " | " + item2);
			}
		}
		File.WriteAllLines(unlockFilePath, list.ToArray());
	}

	private string BuildAdminReport(string command)
	{
		string text = (command ?? string.Empty).Trim().ToLowerInvariant();
		EnsurePortalIndexBuilt();
		if (text.StartsWith("portalinfo|", StringComparison.Ordinal))
		{
			return BuildPortalInformationReport(command);
		}
		if (text == "export")
		{
			SavePortalCounts();
			EnsurePlacementLogHeader();
			EnsureUnlockHistoryHeader();
			return "Portal Limit export refreshed.\nportal_counts.tsv: " + portalCountsFilePath + "\nportal_placements.tsv: " + portalPlacementLogFilePath + "\ndiscovery_unlock_history.tsv: " + unlockHistoryFilePath;
		}
		if (text == "portals")
		{
			return BuildPortalListReport();
		}
		return BuildPlayerCountReport();
	}

	private string BuildPlayerCountReport()
	{
		StringBuilder stringBuilder = new StringBuilder();
		int num = 0;
		foreach (HashSet<ZDOID> value in portalIdsByCreator.Values)
		{
			num += value.Count;
		}
		stringBuilder.AppendLine("Portal Limit server counts");
		stringBuilder.AppendLine("Total counted portals: " + num);
		stringBuilder.AppendLine("Player limit: " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited"));
		List<long> list = new List<long>(portalIdsByCreator.Keys);
		list.Sort((long left, long right) => CountPortals(right).CompareTo(CountPortals(left)));
		foreach (long item in list)
		{
			int num2 = CountPortals(item);
			string creatorName = GetCreatorName(item);
			string text = ((maxPortalsPerPlayer.Value > 0 && num2 > maxPortalsPerPlayer.Value) ? " OVER LIMIT" : string.Empty);
			stringBuilder.AppendLine(num2 + " / " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited") + " - " + creatorName + " (" + item + ")" + text);
		}
		return stringBuilder.ToString().TrimEnd(new char[0]);
	}

	private string BuildPortalListReport()
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine("Portal Limit portal list");
		stringBuilder.AppendLine("playerName | playerId | prefab | tag | role | x,y,z | zdoId");
		List<ZDOID> list = new List<ZDOID>(creatorByPortalId.Keys);
		list.Sort((ZDOID left, ZDOID right) => ((object)(ZDOID)(ref left)).ToString().CompareTo(((object)(ZDOID)(ref right)).ToString()));
		foreach (ZDOID item in list)
		{
			ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(item) : null);
			if (val != null)
			{
				long @long = val.GetLong(ZDOVars.s_creator, 0L);
				Vector3 position = val.GetPosition();
				stringBuilder.AppendLine(GetCreatorName(@long) + " | " + @long + " | " + GetPrefabName(val) + " | " + GetPortalTag(val) + " | " + GetDiscoveryRoleName(val.GetInt("PortalLimit_DiscoveryRole", 0)) + " | " + FormatPosition(position) + " | " + val.m_uid);
			}
		}
		return stringBuilder.ToString().TrimEnd(new char[0]);
	}

	private string BuildPortalInformationReport(string command)
	{
		//IL_017c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		string[] array = (command ?? string.Empty).Split(new char[1] { '|' });
		string fallbackTag = ((array.Length > 1) ? Unescape(array[1]).Trim() : string.Empty);
		string portalKey = ((array.Length > 2) ? Unescape(array[2]).Trim() : string.Empty);
		ZDO zdo = FindPortalZdoByKey(portalKey);
		string text = ResolvePortalInformationTag(zdo, fallbackTag);
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine(BuildServerPortalIdentity(zdo, text));
		stringBuilder.AppendLine();
		stringBuilder.AppendLine("Players who unlocked this portal tag:");
		List<string> list = BuildUnlockLinesForTag(text);
		if (list.Count == 0)
		{
			stringBuilder.AppendLine("No recorded unlocks yet.");
		}
		else
		{
			foreach (string item in list)
			{
				stringBuilder.AppendLine(item);
			}
		}
		StringBuilder stringBuilder2 = new StringBuilder();
		int num = 0;
		foreach (HashSet<ZDOID> value in portalIdsByCreator.Values)
		{
			num += value.Count;
		}
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		foreach (ZDOID key in creatorByPortalId.Keys)
		{
			ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(key) : null);
			switch ((val != null) ? val.GetInt("PortalLimit_DiscoveryRole", 0) : 0)
			{
			case 1:
				num2++;
				break;
			case 2:
				num3++;
				break;
			default:
				num4++;
				break;
			}
		}
		stringBuilder2.AppendLine("Count of all portals built in world: " + num);
		stringBuilder2.AppendLine("Normal portals: " + num4);
		stringBuilder2.AppendLine("Locked entrances: " + num2);
		stringBuilder2.AppendLine("Unlocked entrances: " + num3);
		stringBuilder2.AppendLine("Known portal builders: " + portalIdsByCreator.Count);
		stringBuilder2.AppendLine("Player limit: " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited"));
		stringBuilder2.AppendLine("Tip: blank-tag portals are kept unconnected so players do not get fake teleport links.");
		stringBuilder2.AppendLine("Tip: duplicate locked or unlocked discovery endpoints with the same tag are rejected.");
		return "PortalInfoResponse\n" + stringBuilder.ToString().TrimEnd(new char[0]) + "\n---WORLD---\n" + stringBuilder2.ToString().TrimEnd(new char[0]);
	}

	private ZDO FindPortalZdoByKey(string portalKey)
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: 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)
		if (string.IsNullOrWhiteSpace(portalKey) || ZDOMan.instance == null)
		{
			return null;
		}
		EnsurePortalIndexBuilt();
		foreach (ZDOID key in creatorByPortalId.Keys)
		{
			ZDOID current = key;
			if (!string.Equals(((object)(ZDOID)(ref current)).ToString(), portalKey, StringComparison.Ordinal))
			{
				continue;
			}
			return ZDOMan.instance.GetZDO(current);
		}
		HashSet<string> strictPortalPrefabNameSet = GetStrictPortalPrefabNameSet();
		foreach (string item in strictPortalPrefabNameSet)
		{
			int num = 0;
			List<ZDO> list = new List<ZDO>();
			while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(item, list, ref num))
			{
			}
			foreach (ZDO item2 in list)
			{
				if (item2 != null && string.Equals(((object)(ZDOID)(ref item2.m_uid)).ToString(), portalKey, StringComparison.Ordinal))
				{
					TrackPortal(item2);
					return item2;
				}
			}
		}
		return null;
	}

	private static string ResolvePortalInformationTag(ZDO zdo, string fallbackTag)
	{
		string text = ((zdo != null) ? zdo.GetString("PortalLimit_DiscoveryLink", string.Empty) : string.Empty);
		if (string.IsNullOrWhiteSpace(text))
		{
			text = GetPortalTag(zdo);
		}
		if (string.IsNullOrWhiteSpace(text))
		{
			text = fallbackTag;
		}
		return string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim();
	}

	private string BuildServerPortalIdentity(ZDO zdo, string fallbackTag)
	{
		//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
		if (zdo == null)
		{
			return "Portal Tag: " + (string.IsNullOrWhiteSpace(fallbackTag) ? "(none)" : fallbackTag) + "\nPlayer Who Built the Portal: unknown\nDate the portal was built: unknown\nLast time this portal was used: unknown\nPortal role: unknown\nPortal id: unknown";
		}
		long num = zdo.GetLong(ZDOVars.s_creator, 0L);
		long @long = zdo.GetLong("PortalLimit_BuilderId", 0L);
		if (@long != 0)
		{
			num = @long;
		}
		string text = zdo.GetString("PortalLimit_BuilderName", string.Empty);
		if (string.IsNullOrWhiteSpace(text))
		{
			text = zdo.GetString(ZDOVars.s_creatorName, string.Empty);
		}
		if (string.IsNullOrWhiteSpace(text))
		{
			text = GetCreatorName(num);
		}
		string text2 = zdo.GetString("PortalLimit_DiscoveryLink", string.Empty);
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = GetPortalTag(zdo);
		}
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = (string.IsNullOrWhiteSpace(fallbackTag) ? "(none)" : fallbackTag);
		}
		string text3 = zdo.GetString("PortalLimit_BuiltUtc", string.Empty);
		if (string.IsNullOrWhiteSpace(text3))
		{
			text3 = FindPortalBuiltUtcFromPlacementLog(((object)(ZDOID)(ref zdo.m_uid)).ToString());
		}
		string text4 = FormatDateOnly(text3);
		if (string.IsNullOrWhiteSpace(text4))
		{
			text4 = "unknown";
		}
		string text5 = BuildLastUsedText(zdo);
		string text6 = ((num != 0) ? num.ToString() : "unknown");
		return "Portal Tag: " + text2 + "\nPlayer Who Built the Portal: " + text + ", Steam ID: " + text6 + "\nDate the portal was built: " + text4 + "\nLast time this portal was used: " + text5 + "\nPortal role: " + GetDiscoveryRoleName(zdo.GetInt("PortalLimit_DiscoveryRole", 0)) + "\nPortal id: " + zdo.m_uid;
	}

	private string BuildLastUsedText(ZDO zdo)
	{
		if (zdo == null)
		{
			return "unknown";
		}
		string text = FormatDateTimeText(zdo.GetString("PortalLimit_LastUsedUtc", string.Empty));
		if (string.IsNullOrWhiteSpace(text))
		{
			return "unknown";
		}
		string text2 = zdo.GetString("PortalLimit_LastUsedName", string.Empty);
		long @long = zdo.GetLong("PortalLimit_LastUsedId", 0L);
		if (string.IsNullOrWhiteSpace(text2) && @long != 0)
		{
			text2 = GetCreatorName(@long);
		}
		string text3 = (string.IsNullOrWhiteSpace(text2) ? "unknown" : text2);
		string text4 = ((@long != 0) ? @long.ToString() : "unknown");
		return text + ", by " + text3 + ", Steam ID: " + text4;
	}

	private List<string> BuildUnlockLinesForTag(string tag)
	{
		List<string> list = new List<string>();
		HashSet<long> hashSet = new HashSet<long>();
		if (!string.IsNullOrWhiteSpace(tag))
		{
			foreach (KeyValuePair<long, HashSet<string>> item in unlockedLinksByPlayer)
			{
				if (item.Value.Contains(tag))
				{
					hashSet.Add(item.Key);
				}
			}
		}
		HashSet<long> hashSet2 = new HashSet<long>();
		if (File.Exists(unlockHistoryFilePath))
		{
			string[] array = File.ReadAllLines(unlockHistoryFilePath);
			foreach (string text in array)
			{
				if (string.IsNullOrWhiteSpace(text) || text.StartsWith("timeUtc\t", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { '\t' });
				if (array2.Length >= 4 && string.Equals(array2[3], tag, StringComparison.OrdinalIgnoreCase))
				{
					if (!long.TryParse(array2[2], out var result))
					{
						result = 0L;
					}
					hashSet2.Add(result);
					string text2 = (string.IsNullOrWhiteSpace(array2[1]) ? GetCreatorName(result) : array2[1]);
					string text3 = FormatDateOnly(array2[0]);
					string text4 = FormatTimeOnly(array2[0]);
					list.Add(text2 + ", " + text3 + ", " + text4 + ", " + result);
				}
			}
		}
		foreach (long item2 in hashSet)
		{
			if (!hashSet2.Contains(item2))
			{
				list.Add(GetCreatorName(item2) + ", unknown date, unknown time, " + item2);
			}
		}
		list.Sort(StringComparer.OrdinalIgnoreCase);
		return list;
	}

	private void EnsurePlacementLogHeader()
	{
		if (!string.IsNullOrEmpty(portalPlacementLogFilePath) && !File.Exists(portalPlacementLogFilePath))
		{
			File.WriteAllText(portalPlacementLogFilePath, "timeUtc\tresult\tplayerName\tplayerId\tprefab\tportalTag\trole\tx\ty\tz\tzdoId" + Environment.NewLine);
		}
	}

	private void EnsureUnlockHistoryHeader()
	{
		if (!string.IsNullOrEmpty(unlockHistoryFilePath) && !File.Exists(unlockHistoryFilePath))
		{
			File.WriteAllText(unlockHistoryFilePath, "timeUtc\tplayerName\tplayerId\tportalTag\tunlockPortalId\tx\ty\tz" + Environment.NewLine);
		}
	}

	private string FindPortalBuiltUtcFromPlacementLog(string portalId)
	{
		if (string.IsNullOrWhiteSpace(portalId) || !File.Exists(portalPlacementLogFilePath))
		{
			return string.Empty;
		}
		string[] array = File.ReadAllLines(portalPlacementLogFilePath);
		foreach (string text in array)
		{
			if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("timeUtc\t", StringComparison.OrdinalIgnoreCase))
			{
				string[] array2 = text.Split(new char[1] { '\t' });
				if (array2.Length >= 11 && string.Equals(array2[10], portalId, StringComparison.Ordinal) && string.Equals(array2[1], "PLACED", StringComparison.OrdinalIgnoreCase))
				{
					return array2[0];
				}
			}
		}
		return string.Empty;
	}

	private static string FormatDateOnly(string isoDate)
	{
		if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result))
		{
			return result.ToUniversalTime().ToString("MM/dd/yyyy");
		}
		return "unknown date";
	}

	private static string FormatTimeOnly(string isoDate)
	{
		if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result))
		{
			return result.ToUniversalTime().ToString("HH:mm:ss");
		}
		return "unknown time";
	}

	private static string FormatDateTimeText(string isoDate)
	{
		if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result))
		{
			return result.ToUniversalTime().ToString("MM/dd/yyyy, HH:mm:ss");
		}
		return string.Empty;
	}

	private void AppendUnlockHistoryLog(long playerId, string playerName, string link, string portalId, string x, string y, string z)
	{
		EnsureUnlockHistoryHeader();
		string value = (string.IsNullOrWhiteSpace(playerName) ? GetCreatorName(playerId) : playerName);
		string text = DateTime.UtcNow.ToString("o") + "\t" + EscapeReportField(value) + "\t" + playerId + "\t" + EscapeReportField(link) + "\t" + EscapeReportField(portalId) + "\t" + EscapeReportField(x) + "\t" + EscapeReportField(y) + "\t" + EscapeReportField(z);
		File.AppendAllText(unlockHistoryFilePath, text + Environment.NewLine);
	}

	private void AppendPortalPlacementLog(Piece piece, long creatorId, string result)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent<ZNetView>() : null);
		ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
		if (val2 != null)
		{
			EnsurePlacementLogHeader();
			Vector3 position = val2.GetPosition();
			string text = DateTime.UtcNow.ToString("o") + "\t" + EscapeReportField(result) + "\t" + EscapeReportField(GetCreatorName(creatorId)) + "\t" + creatorId + "\t" + EscapeReportField(GetPrefabName(val2)) + "\t" + EscapeReportField(GetPortalTag(val2)) + "\t" + EscapeReportField(GetDiscoveryRoleName(val2.GetInt("PortalLimit_DiscoveryRole", 0))) + "\t" + position.x.ToString("F1") + "\t" + position.y.ToString("F1") + "\t" + position.z.ToString("F1") + "\t" + val2.m_uid;
			File.AppendAllText(portalPlacementLogFilePath, text + Environment.NewLine);
		}
	}

	private string GetCreatorName(long creatorId)
	{
		string value;
		return (creatorNamesByCreator.TryGetValue(creatorId, out value) && !string.IsNullOrWhiteSpace(value)) ? value : "unknown";
	}

	private static string GetPrefabName(ZDO zdo)
	{
		if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null)
		{
			return "unknown";
		}
		GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab());
		return ((Object)(object)prefab != (Object)null) ? ((Object)prefab).name : zdo.GetPrefab().ToString();
	}

	private static string GetPortalTag(ZDO zdo)
	{
		return (zdo != null) ? zdo.GetString("tag", string.Empty) : string.Empty;
	}

	private static string FormatPosition(Vector3 position)
	{
		return position.x.ToString("F1") + "," + position.y.ToString("F1") + "," + position.z.ToString("F1");
	}

	private static string GetDiscoveryRoleName(int role)
	{
		return role switch
		{
			1 => "LOCKED ENTRANCE", 
			2 => "UNLOCKED ENTRANCE", 
			_ => "NORMAL PORTAL", 
		};
	}

	private void SavePortalCounts()
	{
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(portalCountsFilePath))
		{
			return;
		}
		List<string> list = new List<string>();
		list.Add("# PortalLimit global portal count export");
		list.Add("# Generated from saved Valheim portal ZDO creator data. The world save remains authoritative.");
		list.Add("creatorId\tcreatorName\tportalCount\tportalZdoIds");
		List<long> list2 = new List<long>(portalIdsByCreator.Keys);
		list2.Sort();
		foreach (long item in list2)
		{
			if (!portalIdsByCreator.TryGetValue(item, out var value))
			{
				continue;
			}
			List<string> list3 = new List<string>();
			foreach (ZDOID item2 in value)
			{
				ZDOID current2 = item2;
				list3.Add(((object)(ZDOID)(ref current2)).ToString());
			}
			list3.Sort(StringComparer.Ordinal);
			creatorNamesByCreator.TryGetValue(item, out var value2);
			list.Add(item + "\t" + EscapeReportField(value2) + "\t" + value.Count + "\t" + EscapeReportField(string.Join(",", list3.ToArray())));
		}
		File.WriteAllLines(portalCountsFilePath, list.ToArray());
	}

	private static string EscapeReportField(string value)
	{
		return (value ?? string.Empty).Replace("\t", " ").Replace("\r", " ").Replace("\n", " ");
	}

	private static Dictionary<string, string> ParsePayload(string payload)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		if (string.IsNullOrEmpty(payload))
		{
			return dictionary;
		}
		string[] array = payload.Split(new char[1] { '\n' });
		foreach (string text in array)
		{
			int num = text.IndexOf('=');
			if (num > 0)
			{
				dictionary[text.Substring(0, num)] = Unescape(text.Substring(num + 1));
			}
		}
		return dictionary;
	}

	private static string Escape(string value)
	{
		return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\n", "\\n").Replace("=", "\\e");
	}

	private static string NormalizeMarkerTextValue(string value)
	{
		string text = (value ?? string.Empty).Trim();
		if (string.IsNullOrWhiteSpace(text))
		{
			return "UNLOCK ON OTHER SIDE";
		}
		if (string.Equals(text, "Locked", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "LOCKED", StringComparison.OrdinalIgnoreCase))
		{
			return "LOCKED";
		}
		if (string.Equals(text, "X", StringComparison.OrdinalIgnoreCase))
		{
			return "X";
		}
		if (string.Equals(text, "UnlockOnOtherSide", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "UNLOCK ON OTHER SIDE", StringComparison.OrdinalIgnoreCase))
		{
			return "UNLOCK ON OTHER SIDE";
		}
		return text;
	}

	private static bool TryNormalizeHexColor(string text, out string normalized)
	{
		normalized = (text ?? string.Empty).Trim();
		if (!normalized.StartsWith("#", StringComparison.Ordinal))
		{
			normalized = "#" + normalized;
		}
		Color val = default(Color);
		if (normalized.Length == 7 && ColorUtility.TryParseHtmlString(normalized, ref val))
		{
			return true;
		}
		normalized = string.Empty;
		return false;
	}

	private static string GetPortalTag(TeleportWorld portal)
	{
		if ((Object)(object)portal == (Object)null)
		{
			return string.Empty;
		}
		try
		{
			if (GetPortalTextMethod != null)
			{
				object obj = GetPortalTextMethod.Invoke(portal, null);
				return (obj != null) ? obj.ToString() : string.Empty;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not read portal tag: " + ex.Message));
		}
		ZNetView component = ((Component)portal).GetComponent<ZNetView>();
		ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
		return (val != null) ? val.GetString("tag", string.Empty) : string.Empty;
	}

	private static string Unescape(string value)
	{
		return (value ?? string.Empty).Replace("\\e", "=").Replace("\\n", "\n").Replace("\\\\", "\\");
	}

	private static bool IsDedicatedOrHostServer()
	{
		return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer();
	}
}