Decompiled source of BossGatedPortals v1.3.3

plugins/BossGatedPortals.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BossGatedPortals")]
[assembly: AssemblyProduct("BossGatedPortals")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.3.3")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.3.0")]
[module: UnverifiableCode]
namespace BossGatedPortals;

[HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")]
internal static class Announcements
{
	private static void Prefix(ZoneSystem __instance, string name, out bool __state)
	{
		try
		{
			__state = WasSet(__instance, name);
		}
		catch (Exception e)
		{
			Failsafe.Report("Tier unlock announcement", e);
			__state = true;
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static bool WasSet(ZoneSystem zoneSystem, string name)
	{
		return zoneSystem.GetGlobalKey(name);
	}

	private static void Postfix(ZoneSystem __instance, string name, bool __state)
	{
		try
		{
			Announce(__instance, name, __state);
		}
		catch (Exception e)
		{
			Failsafe.Report("Tier unlock announcement", e);
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static void Announce(ZoneSystem zoneSystem, string name, bool wasSet)
	{
		if (wasSet || !Settings.Enabled.Value || !Settings.AnnounceTierUnlock.Value || !zoneSystem.GetGlobalKey(name))
		{
			return;
		}
		string text = name.Trim().ToLowerInvariant();
		foreach (Tier tier in Settings.Tiers)
		{
			if (!(tier.GlobalKey != text) && !string.IsNullOrEmpty(tier.UnlockMessage))
			{
				Logger.LogInfo((object)$"AnnounceTierUnlock: '{text}' set; announcing T{tier.Index} {tier.Name}.");
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, tier.UnlockMessage });
			}
		}
	}
}
internal static class Bags
{
	public const string BackpacksGUID = "org.bepinex.plugins.backpacks";

	public const string RustyBagsGUID = "RustyMods.RustyBags";

	public const string AdventureBackpacksGUID = "vapok.mods.adventurebackpacks";

	private static bool backpacksResolved;

	private static Func<ItemData, Inventory> backpackInventory;

	private static bool adventureResolved;

	private static Func<ItemData, Inventory> adventureInventory;

	private static Func<ItemData, bool> isAdventureBackpack;

	private static readonly ConditionalWeakTable<SharedData, object> adventureTypes = new ConditionalWeakTable<SharedData, object>();

	private static readonly object IsBag = new object();

	private static readonly object NotBag = new object();

	private static bool rustyResolved;

	private static Type rustyBag;

	private static FieldInfo rustyInventory;

	public static Inventory Contents(ItemData item)
	{
		return RustyBagContents(item) ?? BackpackContents(item) ?? AdventureContents(item);
	}

	public static bool IsBagIn(Inventory carrier, Inventory inventory)
	{
		foreach (ItemData allItem in carrier.GetAllItems())
		{
			if (Contents(allItem) == inventory)
			{
				return true;
			}
		}
		return false;
	}

	private static Inventory AdventureContents(ItemData item)
	{
		if (!ResolveAdventureBackpacks())
		{
			return null;
		}
		try
		{
			if (!adventureTypes.TryGetValue(item.m_shared, out var value))
			{
				adventureTypes.Add(item.m_shared, value = (isAdventureBackpack(item) ? IsBag : NotBag));
			}
			return (value == IsBag) ? adventureInventory(item) : null;
		}
		catch (Exception e)
		{
			Failsafe.Report("AdventureBackpacks bag check", e);
			adventureInventory = null;
			return null;
		}
	}

	private static Inventory BackpackContents(ItemData item)
	{
		if (!ResolveBackpacks())
		{
			return null;
		}
		try
		{
			return backpackInventory(item);
		}
		catch (Exception e)
		{
			Failsafe.Report("Backpacks bag check", e);
			backpackInventory = null;
			return null;
		}
	}

	private static Inventory RustyBagContents(ItemData item)
	{
		if (!ResolveRustyBags() || !rustyBag.IsInstanceOfType(item))
		{
			return null;
		}
		try
		{
			object? value = rustyInventory.GetValue(item);
			return (Inventory)((value is Inventory) ? value : null);
		}
		catch (Exception e)
		{
			Failsafe.Report("RustyBags bag check", e);
			rustyBag = null;
			return null;
		}
	}

	private static bool ResolveBackpacks()
	{
		if (backpacksResolved)
		{
			return backpackInventory != null;
		}
		Assembly assembly = LoadedAssembly("org.bepinex.plugins.backpacks", ref backpacksResolved);
		if (assembly == null)
		{
			return false;
		}
		Type type = assembly.GetType("Backpacks.ItemContainer");
		MethodInfo methodInfo = assembly.GetType("ItemDataManager.ItemExtensions")?.GetMethod("Data", new Type[1] { typeof(ItemData) });
		MethodInfo methodInfo2 = methodInfo?.ReturnType.GetMethods().FirstOrDefault((MethodInfo m) => m.Name == "Get" && m.IsGenericMethodDefinition && m.GetParameters().Length == 1);
		FieldInfo fieldInfo = type?.GetField("Inventory");
		if (type == null || methodInfo2 == null || fieldInfo == null || !typeof(Inventory).IsAssignableFrom(fieldInfo.FieldType))
		{
			ApiChanged("Backpacks");
			return false;
		}
		backpackInventory = DataReader("Backpacks", methodInfo, methodInfo2.MakeGenericMethod(type), fieldInfo);
		Logger.LogInfo((object)"Backpacks detected: items in its bags unlock with their tiers.");
		return true;
	}

	private static bool ResolveAdventureBackpacks()
	{
		if (adventureResolved)
		{
			return adventureInventory != null;
		}
		Assembly assembly = LoadedAssembly("vapok.mods.adventurebackpacks", ref adventureResolved);
		if (assembly == null)
		{
			return false;
		}
		Type type = assembly.GetType("AdventureBackpacks.Components.BackpackComponent");
		MethodInfo methodInfo = assembly.GetType("AdventureBackpacks.Extensions.ItemDataExtensions")?.GetMethod("IsBackpack", new Type[1] { typeof(ItemData) });
		MethodInfo methodInfo2 = assembly.GetType("Vapok.Common.Managers.ItemExtensions")?.GetMethod("Data", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(ItemData) }, null);
		MethodInfo methodInfo3 = methodInfo2?.ReturnType.GetMethods().FirstOrDefault((MethodInfo m) => m.Name == "Get" && m.IsGenericMethodDefinition && m.GetParameters().Length == 1);
		MethodInfo methodInfo4 = type?.GetMethod("GetInventory", Type.EmptyTypes);
		if (type == null || methodInfo == null || methodInfo.ReturnType != typeof(bool) || methodInfo3 == null || methodInfo4 == null || !typeof(Inventory).IsAssignableFrom(methodInfo4.ReturnType))
		{
			Logger.LogWarning((object)"AdventureBackpacks is installed but its bag code changed. With version 2.4.5 or newer, a backpack holding unlocked items can stay blocked until this mod is updated.");
			return false;
		}
		isAdventureBackpack = (Func<ItemData, bool>)Delegate.CreateDelegate(typeof(Func<ItemData, bool>), methodInfo);
		adventureInventory = DataReader("AdventureBackpacks", methodInfo2, methodInfo3.MakeGenericMethod(type), methodInfo4);
		Logger.LogInfo((object)"AdventureBackpacks detected: items in its bags unlock with their tiers.");
		return true;
	}

	private static Func<ItemData, Inventory> DataReader(string mod, MethodInfo data, MethodInfo get, MemberInfo inventory)
	{
		try
		{
			ParameterExpression parameterExpression = Expression.Parameter(typeof(ItemData), "item");
			ParameterExpression parameterExpression2 = Expression.Variable(get.ReturnType, "bag");
			Expression expression = ((inventory is FieldInfo field) ? ((Expression)Expression.Field(parameterExpression2, field)) : ((Expression)Expression.Call(parameterExpression2, (MethodInfo)inventory)));
			return Expression.Lambda<Func<ItemData, Inventory>>(Expression.Block(new ParameterExpression[1] { parameterExpression2 }, Expression.Assign(parameterExpression2, Expression.Call(Expression.Call(data, parameterExpression), get, Expression.Constant(""))), Expression.Condition(Expression.Equal(parameterExpression2, Expression.Constant(null, get.ReturnType)), Expression.Constant(null, typeof(Inventory)), Expression.Convert(expression, typeof(Inventory)))), new ParameterExpression[1] { parameterExpression }).Compile();
		}
		catch (Exception ex)
		{
			Logger.LogInfo((object)(mod + ": using the slower bag check (" + ex.Message + ")."));
			object[] args = new object[1] { "" };
			return delegate(ItemData item)
			{
				object obj = get.Invoke(data.Invoke(null, new object[1] { item }), args);
				if (obj == null)
				{
					return (Inventory)null;
				}
				object? obj2 = ((inventory is FieldInfo fieldInfo) ? fieldInfo.GetValue(obj) : ((MethodInfo)inventory).Invoke(obj, null));
				return (Inventory)((obj2 is Inventory) ? obj2 : null);
			};
		}
	}

	private static bool ResolveRustyBags()
	{
		if (rustyResolved)
		{
			return rustyBag != null;
		}
		Assembly assembly = LoadedAssembly("RustyMods.RustyBags", ref rustyResolved);
		if (assembly == null)
		{
			return false;
		}
		rustyBag = assembly.GetType("RustyBags.Bag");
		rustyInventory = rustyBag?.GetField("inventory");
		if (rustyBag == null || rustyInventory == null || !typeof(ItemData).IsAssignableFrom(rustyBag) || !typeof(Inventory).IsAssignableFrom(rustyInventory.FieldType))
		{
			rustyBag = null;
			ApiChanged("RustyBags");
			return false;
		}
		Logger.LogInfo((object)"RustyBags detected: items in its bags unlock with their tiers.");
		return true;
	}

	private static Assembly LoadedAssembly(string guid, ref bool resolved)
	{
		if (!Chainloader.PluginInfos.TryGetValue(guid, out var value))
		{
			resolved = true;
			return null;
		}
		if ((Object)(object)value.Instance == (Object)null)
		{
			return null;
		}
		resolved = true;
		return ((object)value.Instance).GetType().Assembly;
	}

	private static void ApiChanged(string mod)
	{
		Logger.LogWarning((object)(mod + " is installed but its bag code changed. Items in its bags stay blocked until the final tier (" + mod + "'s own rule) until this mod is updated."));
	}
}
[BepInPlugin("BarryWhite.BossGatedPortals", "BossGatedPortals Plus", "1.3.3")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
internal class BossGatedPortals : BaseUnityPlugin
{
	public const string PluginGUID = "BarryWhite.BossGatedPortals";

	public const string PluginName = "BossGatedPortals Plus";

	private const string OldGUID = "com.jtmill01.bossgatedportals";

	public const string PluginVersion = "1.3.3";

	public const string XPortalGUID = "yay.spikehimself.xportal";

	private readonly Harmony harmony = new Harmony("BarryWhite.BossGatedPortals.harmony");

	private void Awake()
	{
		CarryOverOldConfigFile();
		Settings.Bind(((BaseUnityPlugin)this).Config);
		Settings.LogSummary();
		((BaseUnityPlugin)this).Config.SettingChanged += delegate
		{
			Settings.Parse();
		};
		SynchronizationManager.OnConfigurationSynchronized += delegate
		{
			ReportSettings();
		};
		SynchronizationManager.OnConfigurationWindowClosed += ReportSettings;
		ItemManager.OnItemsRegistered += Settings.Validate;
		PatchEachHook();
		CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new StatusCommand());
		Logger.LogInfo((object)"BossGatedPortals Plus 1.3.3 loaded.");
	}

	private void Start()
	{
		Compatibility.LogInstalledMods();
		Travel.LogStandDown();
		PortalSpeed.LogStandDown();
	}

	private void Update()
	{
		try
		{
			PortalLimit.Tick();
		}
		catch (Exception e)
		{
			Failsafe.Report("Portal limit (counting)", e);
		}
	}

	private void CarryOverOldConfigFile()
	{
		try
		{
			string text = Path.Combine(Paths.ConfigPath, "com.jtmill01.bossgatedportals.cfg");
			if (File.Exists(text) && !File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				File.Copy(text, ((BaseUnityPlugin)this).Config.ConfigFilePath);
				((BaseUnityPlugin)this).Config.Reload();
				File.Move(text, Path.Combine(Paths.ConfigPath, "BarryWhite.BossGatedPortals.cfg.before-1.0.0"));
				Logger.LogInfo((object)("Settings carried over from the old config file to " + Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath) + "."));
			}
		}
		catch (Exception ex)
		{
			Logger.LogWarning((object)("Couldn't carry over the old config file; starting from defaults. " + ex.Message));
		}
	}

	private void PatchEachHook()
	{
		Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(BossGatedPortals).Assembly);
		foreach (Type type in typesFromAssembly)
		{
			try
			{
				harmony.CreateClassProcessor(type).Patch();
			}
			catch (Exception arg)
			{
				Logger.LogError((object)("Couldn't hook the game for " + type.Name + " (game update?). " + $"That feature stays vanilla; the rest of the mod still works. {arg}"));
			}
		}
	}

	private static void ReportSettings()
	{
		Settings.Parse();
		Settings.LogSummary();
		Settings.Validate();
	}
}
internal static class Compatibility
{
	private class SettingConflict
	{
		public readonly string GUID;

		public readonly string Fix;

		public readonly (string Section, string Key, string Value)[] When;

		public SettingConflict(string guid, string fix, params (string, string, string)[] when)
		{
			GUID = guid;
			Fix = fix;
			When = when;
		}
	}

	private static readonly Dictionary<string, string> Conflicts = new Dictionary<string, string>
	{
		{ "unrestrictedportals", "it changes which items portals allow" },
		{ "advancedportals", "it has its own item tiers for portals; two item gates together give unpredictable results" },
		{ "progressionportals", "it has its own progression item gate; two item gates together give unpredictable results" },
		{ "worldadvancementprogression", "it hooks the SAME item check and can switch a block back to allowed; never run it alongside this mod" },
		{ "serversideqol.portalprogression", "it holds blocked items for you near a portal and gives them back after you travel, which skips this gate; remove it or set its Enabled = false" },
		{ "adjustableportals", "its item check runs after this one and turns a block back into allowed whenever your own inventory passes its boss lists, so cart cargo, bag contents and this mod's player requirement stop being gated; don't run it alongside this mod" },
		{ "serversideqol.tameassist", "with its TeleportFollowers on (the default), tames following you are moved to you after a portal trip, so items in their saddlebags skip this mod's tiers; turn TeleportFollowers off, or keep locked items out of saddlebags" },
		{ "teleportationmeads", "its meads teleport you as if every item were allowed, so they carry items this mod's tiers still lock" }
	};

	private static readonly SettingConflict[] SettingConflicts = new SettingConflict[11]
	{
		new SettingConflict("Azumatt.AzuMiscPatches", "set 'Disable Teleport check for items' to Off", ("3 - Inventory & Items", "Disable Teleport check for items", "On")),
		new SettingConflict("org.bepinex.plugins.valheim_plus", "set noTeleportPrevention to false in its [Items] section", ("Items", "enabled", "True"), ("Items", "noTeleportPrevention", "True")),
		new SettingConflict("zenox.teleporteverything", "set TransportRestrictedItems to false in its [Items] section", ("General", "Enabled", "True"), ("Items", "TransportRestrictedItems", "True")),
		new SettingConflict("com.kpro.TeleportEverything", "set 'Transport Ores' to false in its [--- Transport Items ---] section", ("--- Mod ---", "Enable Mod", "True"), ("--- Transport Items ---", "Transport Ores", "True")),
		new SettingConflict("org.bepinex.plugins.targetportal", "set 'Ignore item teleport restrictions' to Default or Never", ("1 - General", "Ignore item teleport restrictions", "Always")),
		new SettingConflict("com.xman0922.unifiedtargetportal", "set IgnoreItemTeleportRestrictions to Default or Never", ("Portals", "IgnoreItemTeleportRestrictions", "Always")),
		new SettingConflict("RustyMods.PortalStations", "set '1 - Teleport Anything' to Off", ("Settings", "1 - Teleport Anything", "On")),
		new SettingConflict("RustyMods.Waypoints", "set '3 - Teleport Anything' to Off", ("2 - Settings", "3 - Teleport Anything", "On")),
		new SettingConflict("shudnal.Waystones", "set 'Ignore nonteleportable items to start search' to false", ("Restrictions", "Ignore nonteleportable items to start search", "True")),
		new SettingConflict("marlthon.ReturnScroll", "set 'Allow Teleport Without Restriction' to Off", ("1 - General", "Allow Teleport Without Restriction", "On")),
		new SettingConflict("sighsorry.PortalRules", "set 'Portal Fare Mode' to Off (Pay lets players pay to carry items this mod's tiers still lock)", ("5 - Portal Travel Costs", "Portal Fare Mode", "Pay"))
	};

	private static readonly HashSet<string> warnedListed = new HashSet<string>();

	public static void LogInstalledMods()
	{
		List<PluginInfo> list = Chainloader.PluginInfos.Values.Where((PluginInfo val4) => val4.Metadata.GUID != "BarryWhite.BossGatedPortals").ToList();
		if (Settings.WarnOnConflictingMods.Value)
		{
			foreach (PluginInfo p in list)
			{
				if (SettingConflicts.Any((SettingConflict c) => c.GUID == p.Metadata.GUID))
				{
					continue;
				}
				string id = Normalize(p.Metadata.GUID) + " " + Normalize(p.Metadata.Name);
				foreach (KeyValuePair<string, string> item in Conflicts.Where((KeyValuePair<string, string> c) => id.Contains(c.Key)))
				{
					Logger.LogWarning((object)("WarnOnConflictingMods: " + Describe(p) + " also changes item teleport rules: " + item.Value + "."));
				}
			}
			SettingConflict[] settingConflicts = SettingConflicts;
			for (int num = 0; num < settingConflicts.Length; num++)
			{
				CheckSetting(settingConflicts[num]);
			}
			PluginInfo val = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo val4) => val4.Metadata.GUID == "com.sweetgiorni.anyportal" || Matches(val4, "anyportal")));
			if (val != null)
			{
				Logger.LogWarning((object)("WarnOnConflictingMods: " + Describe(val) + " is installed. XPortal refuses to load alongside it; remove AnyPortal and use XPortal instead."));
			}
		}
		PluginInfo val2 = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo val4) => val4.Metadata.GUID == "yay.spikehimself.xportal"));
		PluginInfo val3 = ((IEnumerable<PluginInfo>)list).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p2) => Matches(p2, "xportalnetworks")));
		Logger.LogInfo((object)("Portal mods: XPortal " + ((val2 != null) ? Describe(val2) : "not installed") + ", XPortalNetworks " + ((val3 != null) ? Describe(val3) : "not installed") + "."));
	}

	private static void CheckSetting(SettingConflict conflict)
	{
		if (!Chainloader.PluginInfos.TryGetValue(conflict.GUID, out var value) || (Object)(object)value.Instance == (Object)null)
		{
			return;
		}
		bool flag = true;
		bool flag2 = true;
		(string, string, string)[] when = conflict.When;
		for (int i = 0; i < when.Length; i++)
		{
			(string, string, string) tuple = when[i];
			string item = tuple.Item1;
			string item2 = tuple.Item2;
			string item3 = tuple.Item3;
			ConfigEntryBase val = FindSetting(conflict.GUID, item, item2);
			if (val == null)
			{
				flag = false;
				break;
			}
			flag2 &= IsSet(val, item3);
		}
		if (!flag)
		{
			Logger.LogWarning((object)("WarnOnConflictingMods: " + Describe(value) + " can let every item through portals, overriding this mod. Make sure it's off (" + conflict.Fix + ")."));
		}
		else if (flag2)
		{
			Logger.LogWarning((object)("WarnOnConflictingMods: " + Describe(value) + " is set to let every item through portals, which overrides this mod's tiers. To use the tiers, " + conflict.Fix + "."));
		}
	}

	public static void WarnListedButTeleportable(string prefab, Tier tier)
	{
		if (Settings.WarnOnConflictingMods.Value && warnedListed.Add(prefab))
		{
			Logger.LogWarning((object)("WarnOnConflictingMods: '" + prefab + "' is listed in the " + tier.Name + " tier but is teleportable anyway (vanilla, or another mod changed it), so it passes portals before that tier unlocks. To lock it until its tier, set LockListedItemsAlways = true."));
		}
	}

	public static ConfigEntryBase FindSetting(string guid, string section, string key)
	{
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Expected O, but got Unknown
		if (!Chainloader.PluginInfos.TryGetValue(guid, out var value) || (Object)(object)value.Instance == (Object)null)
		{
			return null;
		}
		ConfigFile config = value.Instance.Config;
		ConfigDefinition val = new ConfigDefinition(section, key);
		if (!config.ContainsKey(val))
		{
			return null;
		}
		return config[val];
	}

	public static bool IsSet(ConfigEntryBase entry, string value)
	{
		return string.Equals((entry == null) ? null : entry.BoxedValue?.ToString(), value, StringComparison.OrdinalIgnoreCase);
	}

	private static bool Matches(PluginInfo p, string fragment)
	{
		if (!Normalize(p.Metadata.GUID).Contains(fragment))
		{
			return Normalize(p.Metadata.Name).Contains(fragment);
		}
		return true;
	}

	private static string Normalize(string s)
	{
		return new string((from c in (s ?? "").ToLowerInvariant()
			where c != ' ' && c != '-' && c != '_'
			select c).ToArray());
	}

	private static string Describe(PluginInfo p)
	{
		return $"{p.Metadata.Name} {p.Metadata.Version} ({p.Metadata.GUID})";
	}
}
internal static class Failsafe
{
	private static readonly HashSet<string> reported = new HashSet<string>();

	public static void Report(string where, Exception e)
	{
		if (reported.Add(where))
		{
			Logger.LogError((object)(where + " failed, using vanilla behaviour there instead " + $"(game update?): {e}"));
		}
	}

	public static bool TeleportAllSet()
	{
		return ZoneSystem.instance.GetGlobalKey("teleportall");
	}
}
internal static class Gate
{
	private class FrameState
	{
		public int Frame = -1;

		public Player Player;

		public List<Tier> Tiers;

		public bool TeleportAll;

		public bool[] Earned = new bool[0];
	}

	private static readonly FrameState frame = new FrameState();

	private static readonly ConditionalWeakTable<SharedData, string> prefabNames = new ConditionalWeakTable<SharedData, string>();

	public static bool CanTeleport(Player player, ItemData item, bool portalAllowsAll = false)
	{
		return CollectBlockers(player, item, portalAllowsAll, null, "");
	}

	public static bool CollectBlockers(Player player, ItemData item, bool portalAllowsAll, List<Hints.Blocked> blocked, string where)
	{
		Inventory val = Bags.Contents(item);
		bool vanillaTeleportable = val != null || item.m_shared.m_teleportable;
		bool flag = CanTeleportItem(player, item, vanillaTeleportable, portalAllowsAll);
		if (!flag)
		{
			if (blocked == null)
			{
				return false;
			}
			blocked.Add(new Hints.Blocked
			{
				Item = item,
				Where = where
			});
		}
		if (val != null)
		{
			string text = " in your " + item.m_shared.m_name;
			foreach (ItemData allItem in val.GetAllItems())
			{
				if (!CollectBlockers(player, allItem, portalAllowsAll, blocked, text))
				{
					if (blocked == null)
					{
						return false;
					}
					flag = false;
				}
			}
		}
		return flag;
	}

	private static bool CanTeleportItem(Player player, ItemData item, bool vanillaTeleportable, bool portalAllowsAll)
	{
		bool flag = item.m_shared.m_toolTier >= 1000;
		bool flag2 = portalAllowsAll || ThisFrame(player).TeleportAll;
		if (!Settings.Enabled.Value)
		{
			if (!flag)
			{
				return flag2 || vanillaTeleportable;
			}
			return false;
		}
		string text = PrefabName(item);
		if (text != null && Settings.NeverTeleportItems.Contains(text))
		{
			return false;
		}
		if (flag)
		{
			if (Settings.FinalTierIncludesCheatTier.Value)
			{
				return FinalTierUnlocksEverything(player);
			}
			return false;
		}
		if (flag2 && Settings.RespectVanillaAllowAll.Value)
		{
			return true;
		}
		Tier tier = ((text != null) ? TierFor(text) : null);
		if (vanillaTeleportable && (tier == null || !Settings.LockListedItemsAlways.Value))
		{
			if (tier != null)
			{
				Compatibility.WarnListedButTeleportable(text, tier);
			}
			return true;
		}
		if (tier == null || !IsTierUnlocked(player, tier))
		{
			return FinalTierUnlocksEverything(player);
		}
		return true;
	}

	public static bool IsTierUnlocked(Player player, Tier tier)
	{
		if (IsBypassing(player))
		{
			return true;
		}
		bool[] earned = ThisFrame(player).Earned;
		int num = (Settings.CumulativeTiers.Value ? (earned.Length - 1) : tier.Index);
		for (int i = tier.Index; i <= num && i < earned.Length; i++)
		{
			if (earned[i])
			{
				return true;
			}
		}
		return false;
	}

	private static FrameState ThisFrame(Player player)
	{
		List<Tier> tiers = Settings.Tiers;
		if (frame.Frame == Time.frameCount && (Object)(object)frame.Player == (Object)(object)player && frame.Tiers == tiers)
		{
			return frame;
		}
		frame.Frame = Time.frameCount;
		frame.Player = player;
		frame.Tiers = tiers;
		frame.TeleportAll = Failsafe.TeleportAllSet();
		if (frame.Earned.Length != tiers.Count)
		{
			frame.Earned = new bool[tiers.Count];
		}
		for (int i = 0; i < tiers.Count; i++)
		{
			frame.Earned[i] = IsTierEarned(player, tiers[i]);
		}
		return frame;
	}

	public static bool IsTierEarned(Player player, Tier tier)
	{
		if (Settings.NeedsBossKill && (string.IsNullOrEmpty(tier.GlobalKey) || !ZoneSystem.instance.GetGlobalKey(tier.GlobalKey)))
		{
			return false;
		}
		if (Settings.NeedsBossDrop && (string.IsNullOrEmpty(tier.BossItemToken) || !player.IsMaterialKnown(tier.BossItemToken)))
		{
			return false;
		}
		return true;
	}

	public static Tier TierFor(string prefab)
	{
		if (!Settings.TierOfItem.TryGetValue(prefab, out var value))
		{
			return null;
		}
		return value;
	}

	private static bool FinalTierUnlocksEverything(Player player)
	{
		List<Tier> tiers = Settings.Tiers;
		if (tiers.Count == 0)
		{
			return false;
		}
		Tier tier = tiers[tiers.Count - 1];
		if (tier.AllowEverything)
		{
			return IsTierUnlocked(player, tier);
		}
		return false;
	}

	private static bool IsBypassing(Player player)
	{
		if (Settings.AdminBypass.Value && (Object)(object)player == (Object)(object)Player.m_localPlayer)
		{
			return SynchronizationManager.Instance.PlayerIsAdmin;
		}
		return false;
	}

	public static string PrefabName(ItemData item)
	{
		if (prefabNames.TryGetValue(item.m_shared, out var value))
		{
			return value;
		}
		GameObject val = default(GameObject);
		if (Object.op_Implicit((Object)(object)item.m_dropPrefab))
		{
			value = ((Object)item.m_dropPrefab).name;
		}
		else if (Object.op_Implicit((Object)(object)ObjectDB.instance) && ObjectDB.instance.TryGetItemPrefab(item.m_shared, ref val) && Object.op_Implicit((Object)(object)val))
		{
			value = ((Object)val).name;
		}
		if (value != null)
		{
			prefabNames.Remove(item.m_shared);
			prefabNames.Add(item.m_shared, value);
		}
		return value;
	}
}
[HarmonyPatch(typeof(Inventory), "IsTeleportable")]
internal static class InventoryIsTeleportablePatch
{
	private static int ownCheckDepth;

	private static bool ownAllowAll;

	private static readonly List<Hints.Blocked> scratch = new List<Hints.Blocked>();

	[HarmonyPriority(800)]
	private static void Prefix(Inventory __instance, bool allowAllItems, out bool __state)
	{
		__state = false;
		try
		{
			__state = EnterOwnCheck(__instance, allowAllItems);
		}
		catch (Exception e)
		{
			Failsafe.Report("Portal item check (start)", e);
		}
	}

	[HarmonyPriority(800)]
	private static void Postfix(Inventory __instance, bool allowAllItems, ref bool __result)
	{
		try
		{
			Gatekeep(__instance, allowAllItems, ref __result);
		}
		catch (Exception e)
		{
			Failsafe.Report("Portal item check", e);
		}
	}

	private static void Finalizer(bool __state)
	{
		if (__state)
		{
			ownCheckDepth--;
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static bool EnterOwnCheck(Inventory inventory, bool allowAllItems)
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || ((Humanoid)localPlayer).GetInventory() != inventory)
		{
			return false;
		}
		if (ownCheckDepth++ == 0)
		{
			ownAllowAll = allowAllItems;
		}
		return true;
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static void Gatekeep(Inventory inventory, bool allowAllItems, ref bool result)
	{
		Player localPlayer = Player.m_localPlayer;
		if (!Settings.Enabled.Value || (Object)(object)localPlayer == (Object)null)
		{
			return;
		}
		Vagon val = AttachedCart(localPlayer);
		Inventory val2 = (Object.op_Implicit((Object)(object)val) ? val.m_container.GetInventory() : null);
		bool flag = ((Humanoid)localPlayer).GetInventory() == inventory;
		if (!flag && ownCheckDepth > 0)
		{
			scratch.Clear();
			AddBlocked(localPlayer, inventory, allowAllItems, " in your backpack", scratch);
			if (allowAllItems == ownAllowAll)
			{
				foreach (Hints.Blocked item in scratch)
				{
					if (!Listed(item.Item))
					{
						Hints.LastBlocked.Add(item);
					}
				}
			}
			result = scratch.Count == 0;
			return;
		}
		if (!flag && inventory != val2)
		{
			if (!Bags.IsBagIn(((Humanoid)localPlayer).GetInventory(), inventory))
			{
				return;
			}
			result = true;
			{
				foreach (ItemData allItem in inventory.GetAllItems())
				{
					result &= Gate.CanTeleport(localPlayer, allItem, allowAllItems);
				}
				return;
			}
		}
		Hints.LastBlocked.Clear();
		Hints.PortalLocked = null;
		if (flag && !PortalLimit.PortalsUsable(allowAllItems, out var message))
		{
			Hints.PortalLocked = message;
			result = false;
			return;
		}
		AddBlocked(localPlayer, inventory, allowAllItems, flag ? "" : (" in your " + val.GetHoverName()));
		if (flag && val2 != null)
		{
			AddBlocked(localPlayer, val2, allowAllItems, " in your " + val.GetHoverName());
		}
		if (flag)
		{
			foreach (Tames.Bag item2 in Tames.Cargo(localPlayer))
			{
				AddBlocked(localPlayer, item2.Inventory, allowAllItems, item2.Where);
			}
		}
		result = Hints.LastBlocked.Count == 0;
	}

	private static bool Listed(ItemData item)
	{
		foreach (Hints.Blocked item2 in Hints.LastBlocked)
		{
			if (item2.Item == item)
			{
				return true;
			}
		}
		return false;
	}

	private static void AddBlocked(Player player, Inventory inventory, bool allowAllItems, string where, List<Hints.Blocked> blocked = null)
	{
		foreach (ItemData allItem in inventory.GetAllItems())
		{
			Gate.CollectBlockers(player, allItem, allowAllItems, blocked ?? Hints.LastBlocked, where);
		}
	}

	private static Vagon AttachedCart(Player player)
	{
		if (!Settings.GateCompanionCargo.Value && (!Travel.atPortal || !Travel.BringsCart))
		{
			return null;
		}
		Vagon val = Travel.CartPulledBy((Character)(object)player);
		if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.m_container))
		{
			return null;
		}
		return val;
	}
}
internal static class Hints
{
	public struct Blocked
	{
		public ItemData Item;

		public string Where;
	}

	[HarmonyPatch(typeof(Player), "Message")]
	private static class PlayerMessagePatch
	{
		private static bool Prefix(Player __instance, ref MessageType type, ref string msg)
		{
			try
			{
				return SwapForHint(__instance, ref type, ref msg);
			}
			catch (Exception e)
			{
				Failsafe.Report("Unlock hint", e);
				return true;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool SwapForHint(Player player, ref MessageType type, ref string msg)
		{
			if (msg != "$msg_noteleport" || (Object)(object)player != (Object)(object)Player.m_localPlayer || !Settings.Enabled.Value || (PortalLocked == null && string.IsNullOrEmpty(Settings.HintFormat.Value)))
			{
				return true;
			}
			string text = PortalLocked ?? BuildMessage();
			if (text == null)
			{
				return true;
			}
			if (Time.time - lastHintTime < Settings.HintCooldownSeconds.Value)
			{
				return false;
			}
			lastHintTime = Time.time;
			msg = text;
			type = (MessageType)((Settings.HintPositionSetting.Value == HintPosition.TopLeft) ? 1 : 2);
			return true;
		}
	}

	public static readonly List<Blocked> LastBlocked = new List<Blocked>();

	public static string PortalLocked;

	public const string OldDefaultFormat = "An item blocks the portal. {hint}";

	public const string DefaultFormat = "An item{where} blocks the portal. {hint}";

	private static float lastHintTime = float.NegativeInfinity;

	public static string BuildMessage()
	{
		List<Tier> tiers = Settings.Tiers;
		if (tiers.Count == 0)
		{
			return null;
		}
		Tier tier = tiers[tiers.Count - 1];
		Tier tier2 = null;
		Blocked blocked = default(Blocked);
		foreach (Blocked item2 in LastBlocked)
		{
			ItemData item = item2.Item;
			string text = Gate.PrefabName(item);
			if (text != null && Settings.NeverTeleportItems.Contains(text))
			{
				continue;
			}
			Tier tier3;
			if (item.m_shared.m_toolTier >= 1000)
			{
				if (!Settings.FinalTierIncludesCheatTier.Value || !tier.AllowEverything)
				{
					continue;
				}
				tier3 = tier;
			}
			else
			{
				tier3 = ((text != null) ? Gate.TierFor(text) : null) ?? tier;
			}
			if (tier2 == null || tier3.Index < tier2.Index)
			{
				tier2 = tier3;
				blocked = item2;
			}
		}
		if (tier2 == null || string.IsNullOrEmpty(tier2.Hint))
		{
			return null;
		}
		return Settings.HintFormat.Value.Replace("{hint}", tier2.Hint).Replace("{item}", blocked.Item.m_shared.m_name).Replace("{where}", blocked.Where ?? "");
	}

	public static void LogUntieredItems()
	{
		if ((Object)(object)ObjectDB.instance == (Object)null)
		{
			return;
		}
		List<string> list = new List<string>();
		foreach (GameObject item in ObjectDB.instance.m_items)
		{
			ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val == (Object)null) && !val.m_itemData.m_shared.m_teleportable && !Settings.NeverTeleportItems.Contains(((Object)item).name) && Gate.TierFor(((Object)item).name) == null)
			{
				list.Add(((Object)item).name);
			}
		}
		if (list.Count == 0)
		{
			Logger.LogInfo((object)"LogItemChecks: every teleport-blocked item is listed in a tier.");
			return;
		}
		Logger.LogInfo((object)("LogItemChecks: teleport-blocked items not in any tier (locked until the final tier): " + string.Join(", ", list.OrderBy((string n) => n))));
	}
}
internal static class PortalLimit
{
	[HarmonyPatch(typeof(Game), "Start")]
	private static class GameStartPatch
	{
		private static void Postfix()
		{
			try
			{
				Register();
			}
			catch (Exception e)
			{
				Failsafe.Report("Portal limit (count message)", e);
			}
		}
	}

	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	private static class TryPlacePiecePatch
	{
		private static bool Prefix(Player __instance, Piece piece)
		{
			try
			{
				return MayPlace(__instance, piece);
			}
			catch (Exception e)
			{
				Failsafe.Report("Portal limit", e);
				return true;
			}
		}

		private static void Postfix(Player __instance, Piece piece, bool __result)
		{
			try
			{
				if (__result)
				{
					Placed(__instance, piece);
				}
			}
			catch (Exception e)
			{
				Failsafe.Report("Portal limit (count)", e);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool MayPlace(Player player, Piece piece)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !IsCountedPortal(piece))
			{
				return true;
			}
			if (CanBuildPortal(out var message))
			{
				return true;
			}
			((Character)player).Message((MessageType)2, message, 0, (Sprite)null, false);
			return false;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void Placed(Player player, Piece piece)
		{
			if ((Object)(object)player == (Object)(object)Player.m_localPlayer && IsCountedPortal(piece))
			{
				CountPlaced();
			}
		}

		private static bool IsCountedPortal(Piece piece)
		{
			if (!Object.op_Implicit((Object)(object)piece) || !Object.op_Implicit((Object)(object)Game.instance))
			{
				return false;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)((Component)piece).gameObject).name);
			if (!Game.instance.PortalPrefabHash.Contains(stableHashCode))
			{
				return false;
			}
			if (!Settings.PortalLimitCountStonePortals.Value)
			{
				return !AllowsAllItems(stableHashCode);
			}
			return true;
		}
	}

	private const string CountRpc = "BossGatedPortals_PortalCount";

	private const float CountSeconds = 2f;

	public const string WorldKey = "bgp_portallimit";

	private const float DecideAfterSeconds = 3f;

	private static float gameStartTime = float.PositiveInfinity;

	private static bool decided;

	private static int knownCount = -1;

	private static float nextCount;

	private static int limitFrame = -1;

	private static int frameLimit;

	private static int countFrame = -1;

	private static int frameCount;

	private static readonly Dictionary<int, bool> allowsAll = new Dictionary<int, bool>();

	public static bool CanBuildPortal(out string message)
	{
		message = null;
		if (!Active())
		{
			return true;
		}
		if (Settings.PortalLimitAdminsExempt.Value && SynchronizationManager.Instance.PlayerIsAdmin)
		{
			return true;
		}
		int num = Limit();
		if (num < 0)
		{
			return true;
		}
		int num2 = Count();
		if (num2 < 0)
		{
			message = "Counting this world's portals; try again in a moment.";
			return false;
		}
		if (num2 < num)
		{
			return true;
		}
		message = Settings.PortalLimitMessage.Value.Replace("{count}", num2.ToString()).Replace("{limit}", num.ToString());
		return false;
	}

	public static bool PortalsUsable(bool portalAllowsAll, out string message)
	{
		message = null;
		if (!Settings.PortalLimitLockWhenOver.Value || !Active())
		{
			return true;
		}
		if (portalAllowsAll && !Settings.PortalLimitCountStonePortals.Value)
		{
			return true;
		}
		int num = Limit();
		int num2 = Count();
		if (num < 0 || num2 <= num)
		{
			return true;
		}
		message = Settings.PortalLimitOverMessage.Value.Replace("{count}", num2.ToString()).Replace("{limit}", num.ToString());
		return false;
	}

	public static bool Active()
	{
		if (Settings.Enabled.Value && Settings.PortalLimitEnabled.Value)
		{
			return WorldChoice() == true;
		}
		return false;
	}

	public static bool? WorldChoice()
	{
		string text = default(string);
		if ((Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.GetGlobalKey("bgp_portallimit", ref text))
		{
			return null;
		}
		return text == "on";
	}

	public static void SetWorldChoice(bool on)
	{
		ZoneSystem.instance.SetGlobalKey("bgp_portallimit" + (on ? " on" : " off"));
	}

	public static int Limit()
	{
		if (limitFrame == Time.frameCount)
		{
			return frameLimit;
		}
		limitFrame = Time.frameCount;
		frameLimit = LimitNow();
		return frameLimit;
	}

	private static int LimitNow()
	{
		int num = Settings.PortalLimitStart.Value;
		foreach (Tier tier in Settings.Tiers)
		{
			if (!string.IsNullOrEmpty(tier.GlobalKey) && ZoneSystem.instance.GetGlobalKey(tier.GlobalKey))
			{
				if (tier.MaxPortals < 0)
				{
					return -1;
				}
				num = Math.Max(num, tier.MaxPortals);
			}
		}
		return num;
	}

	public static int Count()
	{
		if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer())
		{
			return knownCount;
		}
		if (countFrame != Time.frameCount)
		{
			countFrame = Time.frameCount;
			frameCount = CountNow();
		}
		return frameCount;
	}

	private static int CountNow()
	{
		bool value = Settings.PortalLimitCountStonePortals.Value;
		int num = 0;
		foreach (List<ZDO> value2 in ZDOMan.instance.GetPortals().Values)
		{
			if (value)
			{
				num += value2.Count;
				continue;
			}
			foreach (ZDO item in value2)
			{
				if (!AllowsAllItems(item.GetPrefab()))
				{
					num++;
				}
			}
		}
		return num;
	}

	public static bool AllowsAllItems(int prefabHash)
	{
		if (allowsAll.TryGetValue(prefabHash, out var value))
		{
			return value;
		}
		GameObject val = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(prefabHash) : null);
		if (!Object.op_Implicit((Object)(object)val))
		{
			return false;
		}
		TeleportWorld componentInChildren = val.GetComponentInChildren<TeleportWorld>(true);
		value = Object.op_Implicit((Object)(object)componentInChildren) && componentInChildren.m_allowAllItems;
		allowsAll[prefabHash] = value;
		return value;
	}

	public static void CountPlaced()
	{
		if (knownCount >= 0)
		{
			knownCount++;
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	public static void Tick()
	{
		if (Time.time < nextCount)
		{
			return;
		}
		nextCount = Time.time + 2f;
		if (!Settings.Enabled.Value || !Settings.PortalLimitEnabled.Value || !Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer() || ZRoutedRpc.instance == null || ZDOMan.instance == null || (Object)(object)ZoneSystem.instance == (Object)null || Time.time < gameStartTime + 3f)
		{
			return;
		}
		int num = CountNow();
		if (!WorldChoice().HasValue)
		{
			if (!decided)
			{
				decided = true;
				bool num2 = num == 0;
				SetWorldChoice(num2);
				Logger.LogInfo((object)(num2 ? "Portal limit: this world has no portals yet, so the limit applies here." : ($"Portal limit: this world already has {num} portals, so the limit is left off here. " + "An admin can turn it on with 'portalgate limit on'.")));
			}
		}
		else if (Active())
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "BossGatedPortals_PortalCount", new object[1] { num });
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	public static void Register()
	{
		knownCount = -1;
		decided = false;
		gameStartTime = Time.time;
		ZRoutedRpc.instance.Register<int>("BossGatedPortals_PortalCount", (Action<long, int>)delegate(long sender, int count)
		{
			knownCount = count;
		});
	}
}
internal static class PortalSpeed
{
	[HarmonyPatch(typeof(Hud), "GetFadeDuration")]
	private static class FadePatch
	{
		private static void Postfix(Player player, ref float __result)
		{
			try
			{
				__result = TeleportFade(player, __result);
			}
			catch (Exception e)
			{
				Failsafe.Report("Teleport fade time", e);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static float TeleportFade(Player player, float vanilla)
		{
			if (!Active() || (Object)(object)player == (Object)null || ((Character)player).IsDead() || player.IsSleeping())
			{
				return vanilla;
			}
			return Mathf.Max(Fade(), 0.01f);
		}
	}

	[HarmonyPatch(typeof(Player), "UpdateTeleport")]
	private static class UpdateTeleportPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			FieldInfo timer = AccessTools.Field(typeof(Player), "m_teleportTimer");
			int num = FindAfterTimer(list, timer, 2f);
			int num2 = FindAfterTimer(list, timer, 8f);
			if (num < 0 || num2 < 0)
			{
				Logger.LogWarning((object)"MinimumLoadingSeconds: couldn't find the teleport wait in the game's code (game update, or another mod changed it). Teleports keep their usual wait.");
				return list;
			}
			list[num].opcode = OpCodes.Call;
			list[num].operand = AccessTools.Method(typeof(PortalSpeed), "MoveDelay", (Type[])null, (Type[])null);
			list[num2].opcode = OpCodes.Call;
			list[num2].operand = AccessTools.Method(typeof(PortalSpeed), "Hold", (Type[])null, (Type[])null);
			return list;
		}

		private static int FindAfterTimer(List<CodeInstruction> codes, FieldInfo timer, float value)
		{
			if (timer == null)
			{
				return -1;
			}
			for (int i = 1; i < codes.Count; i++)
			{
				if (codes[i].opcode == OpCodes.Ldc_R4 && codes[i].operand is float num && num == value && CodeInstructionExtensions.LoadsField(codes[i - 1], timer, false))
				{
					return i;
				}
			}
			return -1;
		}
	}

	private static readonly string[] SpeedMods = new string[4] { "elg.QuickTeleport", "muindor.QuickTeleport", "GemHunter1.FastTeleport", "dev.crystal.properportals" };

	private static bool resolved;

	private static string otherMod;

	private const float VanillaFade = 1f;

	private const float VanillaMoveDelay = 2f;

	private const float VanillaHold = 8f;

	private const float MinimumMoveDelay = 0.25f;

	private const float MinimumFade = 0.01f;

	public static string OtherMod()
	{
		if (!resolved)
		{
			resolved = true;
			string[] speedMods = SpeedMods;
			foreach (string key in speedMods)
			{
				if (Chainloader.PluginInfos.TryGetValue(key, out var value) && (Object)(object)value.Instance != (Object)null)
				{
					otherMod = value.Metadata.Name;
					break;
				}
			}
		}
		return otherMod;
	}

	public static void LogStandDown()
	{
		if (OtherMod() != null)
		{
			bool num = Settings.FadeSeconds.Value != 1f || Settings.MinimumLoadingSeconds.Value != 8f;
			string text = "Portal Speed: " + OtherMod() + " is installed and speeds up portals itself, so [4 - Portal Speed] leaves the timings to it. Use its settings instead.";
			if (num)
			{
				Logger.LogWarning((object)text);
			}
			else
			{
				Logger.LogInfo((object)text);
			}
		}
	}

	public static float MoveDelay()
	{
		try
		{
			return Mathf.Clamp(Mathf.Max(Loading(), Fade()), 0.25f, 2f);
		}
		catch (Exception e)
		{
			Failsafe.Report("Teleport loading time", e);
			return 2f;
		}
	}

	public static float Hold()
	{
		try
		{
			return Mathf.Clamp(Loading(), 0f, 8f);
		}
		catch (Exception e)
		{
			Failsafe.Report("Teleport loading time", e);
			return 8f;
		}
	}

	private static bool Active()
	{
		if (Settings.Enabled.Value)
		{
			return OtherMod() == null;
		}
		return false;
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static float Loading()
	{
		if (!Active())
		{
			return 8f;
		}
		return Settings.MinimumLoadingSeconds.Value;
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static float Fade()
	{
		if (!Active())
		{
			return 1f;
		}
		return Settings.FadeSeconds.Value;
	}
}
public enum HintPosition
{
	Centered,
	TopLeft
}
public enum UnlockRule
{
	BossKilled,
	BossKilledAndDropHeld,
	DropHeld
}
public enum TameSelection
{
	Following,
	FollowingOrNamed,
	Named,
	AllTamed
}
internal static class Settings
{
	private class TierEntries
	{
		public string Name;

		public ConfigEntry<string> GlobalKey;

		public ConfigEntry<string> BossItem;

		public ConfigEntry<string> Items;

		public ConfigEntry<string> Hint;

		public ConfigEntry<string> UnlockMessage;

		public ConfigEntry<bool> AllowEverything;

		public ConfigEntry<int> MaxPortals;
	}

	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<UnlockRule> UnlockWhen;

	public static ConfigEntry<bool> CumulativeTiers;

	public static ConfigEntry<string> NeverTeleport;

	public static ConfigEntry<bool> HildirChestsByTier;

	public static ConfigEntry<bool> LockListedItemsAlways;

	public static ConfigEntry<bool> FinalTierIncludesCheatTier;

	public static ConfigEntry<bool> RespectVanillaAllowAll;

	public static ConfigEntry<string> HintFormat;

	public static ConfigEntry<bool> AnnounceTierUnlock;

	public static ConfigEntry<bool> AccurateTooltips;

	public static ConfigEntry<float> FadeSeconds;

	public static ConfigEntry<float> MinimumLoadingSeconds;

	public static ConfigEntry<bool> BringMount;

	public static ConfigEntry<bool> BringCart;

	public static ConfigEntry<bool> BringTames;

	public static ConfigEntry<TameSelection> TameMode;

	public static ConfigEntry<float> TameRadius;

	public static ConfigEntry<float> TameHeightRange;

	public static ConfigEntry<int> TameMaxCount;

	public static ConfigEntry<string> TameAllowList;

	public static ConfigEntry<string> TameBlockList;

	public static ConfigEntry<bool> TameIncludeSummons;

	public static ConfigEntry<bool> GateCompanionCargo;

	public static ConfigEntry<bool> WarnOnConflictingMods;

	public static ConfigEntry<bool> AdminBypass;

	public static ConfigEntry<bool> StatusCommand;

	public static ConfigEntry<bool> LogItemChecks;

	public static ConfigEntry<HintPosition> HintPositionSetting;

	public static ConfigEntry<float> HintCooldownSeconds;

	public static ConfigEntry<bool> PortalLimitEnabled;

	public static ConfigEntry<int> PortalLimitStart;

	public static ConfigEntry<bool> PortalLimitAdminsExempt;

	public static ConfigEntry<string> PortalLimitMessage;

	public static ConfigEntry<bool> PortalLimitLockWhenOver;

	public static ConfigEntry<string> PortalLimitOverMessage;

	public static ConfigEntry<bool> PortalLimitCountStonePortals;

	public static List<Tier> Tiers = new List<Tier>();

	public static HashSet<string> NeverTeleportItems = new HashSet<string>();

	public static Dictionary<string, Tier> TierOfItem = new Dictionary<string, Tier>();

	public static HashSet<string> TameAllow = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	public static HashSet<string> TameBlock = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static readonly List<TierEntries> tierEntries = new List<TierEntries>();

	private static readonly string[][] TierDefaults = new string[8][]
	{
		new string[6] { "Meadows", "defeated_eikthyr", "HardAntler", "", "", "" },
		new string[6] { "Black Forest", "defeated_gdking", "CryptKey", "CopperOre, Copper, CopperScrap, TinOre, Tin, Bronze, BronzeScrap", "Something old still stirs beneath the Black Forest canopy.", "The portals hum. Metals of the forest may now pass." },
		new string[6] { "Swamp", "defeated_bonemass", "Wishbone", "IronOre, IronScrap, Iron, Ironpit", "The Swamp's guardian has not yet been put to rest.", "The portals hum. Iron from the Swamp may now pass." },
		new string[6] { "Mountains", "defeated_dragon", "DragonTear", "SilverOre, Silver, DragonEgg", "The Mountain peaks answer to a ruler you have not faced.", "The portals hum. Treasures of the Mountains may now pass." },
		new string[6] { "Plains", "defeated_goblinking", "YagluthDrop", "BlackMetalScrap, BlackMetal", "The Plains still bow to their fallen king.", "The portals hum. Black metal may now pass." },
		new string[6] { "Mistlands", "defeated_queen", "QueenDrop", "MechanicalSpring, DvergrNeedle", "Something waits deep within the Mistlands.", "The portals hum. Relics of the Mistlands may now pass." },
		new string[6] { "Ashlands", "defeated_fader", "FaderDrop", "FlametalOreNew, FlametalNew, FlametalOre, Flametal, CharredCogwheel", "The Ashlands' guardian still smolders.", "The portals hum. Flametal may now pass." },
		new string[6] { "Deep North", "defeated_frozenking_p3", "FrozenKingDrop", "GoldOre, Gold", "The portal will not carry this until the far North is conquered.", "The portals roar. Nothing is barred from passage now." }
	};

	private static readonly int[] TierMaxPortals = new int[8] { 4, 6, 8, 10, 12, 16, 20, -1 };

	private static readonly Dictionary<string, int> HildirChestTiers = new Dictionary<string, int>
	{
		{ "chest_hildir1", 1 },
		{ "chest_hildir2", 3 },
		{ "chest_hildir3", 4 }
	};

	private static ConfigFile config;

	private static int order;

	public static bool NeedsBossKill => UnlockWhen.Value != UnlockRule.DropHeld;

	public static bool NeedsBossDrop => UnlockWhen.Value != UnlockRule.BossKilled;

	public static void Bind(ConfigFile cfg)
	{
		config = cfg;
		bool saveOnConfigSet = cfg.SaveOnConfigSet;
		cfg.SaveOnConfigSet = false;
		try
		{
			BindEntries();
			Migrate();
		}
		finally
		{
			cfg.Save();
			cfg.SaveOnConfigSet = saveOnConfigSet;
		}
		Parse();
	}

	private static void BindEntries()
	{
		Enabled = Admin("1 - General", "Enabled", value: true, "Master switch for the mod.");
		UnlockWhen = Admin("1 - General", "UnlockWhen", UnlockRule.BossKilled, "What unlocks a tier. BossKilled = the boss has been killed on this world (for everyone). BossKilledAndDropHeld = also, each player must have picked up the boss's drop at least once. DropHeld = only the player's own boss drop counts.");
		CumulativeTiers = Admin("1 - General", "CumulativeTiers", value: false, "If true, unlocking a later tier also unlocks every earlier tier for that player. false = each tier must be earned on its own.");
		NeverTeleport = Admin("2 - Item Rules", "NeverTeleport", "", "Items that can NEVER be teleported, even after the final tier. Prefab names, comma-separated. Example: DragonEgg");
		HildirChestsByTier = Admin("2 - Item Rules", "HildirChestsByTier", value: false, "If true, Hildir's chests unlock with the tier of the biome they're found in (Brass = Black Forest, Silver = Mountains, Bronze = Plains). false = they never teleport, as in vanilla.");
		LockListedItemsAlways = Admin("2 - Item Rules", "LockListedItemsAlways", value: false, "If true, every item listed in a tier stays locked until that tier, even items the game (or another mod) would let through portals anyway. Use it to lock extra items by progression, e.g. Coal in the Black Forest tier. false = tiers only unlock items the game blocks; listing an item the game already allows does nothing.");
		FinalTierIncludesCheatTier = Admin("2 - Item Rules", "FinalTierIncludesCheatTier", value: false, "Vanilla blocks tool-tier 1000+ items at every portal. The only such item is FrozenKingDrop. If true, the final tier also unlocks those.");
		RespectVanillaAllowAll = Admin("2 - Item Rules", "RespectVanillaAllowAll", value: true, "Stone Portals: in vanilla the Stone Portal is the one portal that lets every item through, ores and metals included. true = keep it that way: the tiers never block items at a Stone Portal (or anywhere, while the 'Portals' world modifier allows all items). false = Stone Portals follow the tiers like wood portals. Only about items; the portal limit has its own Stone Portal setting (PortalLimitCountStonePortals).");
		HintFormat = Admin("3 - Messages", "HintFormat", "An item{where} blocks the portal. {hint}", "Shown when a locked item stops you at a portal. {hint} = the earliest locked tier's Hint. {where} = where that item is, if not in your inventory (\" in your Cart\", \" in Lox's saddlebags\", \" in your backpack\"). Optional {item} names the item. Never names a boss. Empty = the game's usual message instead.");
		AnnounceTierUnlock = Admin("3 - Messages", "AnnounceTierUnlock", value: true, "When a boss's world key is first set, announce to everyone that portals accept more goods. Uses the tier's UnlockMessage; never names a boss.");
		AccurateTooltips = Admin("3 - Messages", "AccurateTooltips", value: true, "Item tooltips and inventory icons show whether the item can be teleported RIGHT NOW for this player, using the same check the portal uses.");
		FadeSeconds = Admin("4 - Portal Speed", "FadeSeconds", 1f, "How long the screen takes to fade to black when you step into a portal, and back in when you arrive. 1 = the game's usual. 0 = the teleport screen appears the moment you touch the portal. Does nothing while QuickTeleport, FastTeleport or Proper Portals is installed (they set this themselves).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f));
		MinimumLoadingSeconds = Admin("4 - Portal Speed", "MinimumLoadingSeconds", 8f, "Shortest time a teleport's loading screen lasts. 8 = the game's usual wait. Lower it to cut the built-in delay, down to 0 for none. The screen still stays up while the destination is loading, and you're only moved once the screen is black (and after at least a quarter second, so a pulled cart can let go first). Does nothing while QuickTeleport, FastTeleport or Proper Portals is installed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f));
		BringMount = Admin("5 - Travel", "BringMount", value: false, "Ride into a portal and your mount comes with you; you're put back in the saddle on the other side. Items in its saddlebags are gated like your own. Does nothing while TeleportEverything is on (either version).");
		BringCart = Admin("5 - Travel", "BringCart", value: false, "A cart you're pulling comes through a portal with you and is hitched back up on the other side. Its cargo is gated like your own inventory. Does nothing while TeleportEverything is on (either version).");
		BringTames = Admin("5 - Travel", "BringTames", value: false, "Tames near you come through a portal with you. The Tame... settings right below choose which, and only apply while this is on. Items in their saddlebags are gated like your own. Does nothing while TeleportEverything is on (either version).");
		TameMode = Admin("5 - Travel", "TameMode", TameSelection.Following, "Only used when BringTames is on. Which tames come along. Following = only tames following you. FollowingOrNamed = also tames you've named. Named = only named tames. AllTamed = every tame in range (penned animals too). Tames following another player never come.");
		TameRadius = Admin("5 - Travel", "TameRadius", 10f, "Only used when BringTames is on. How far from you (metres, sideways) a tame can be and still come along.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f));
		TameHeightRange = Admin("5 - Travel", "TameHeightRange", 3f, "Only used when BringTames is on. How far above or below you (metres) a tame can be and still come along. Keeps animals on other floors of your base behind.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 50f));
		TameMaxCount = Admin("5 - Travel", "TameMaxCount", 5, "Only used when BringTames is on. Most tames one trip takes along. The nearest come first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50));
		TameAllowList = Admin("5 - Travel", "TameAllowList", "", "Only used when BringTames is on. If not empty, only these creatures can come along. Prefab names, comma-separated. Example: Wolf, Lox, Boar, Hen, Asksvin");
		TameBlockList = Admin("5 - Travel", "TameBlockList", "", "Only used when BringTames is on. These creatures never come along. Prefab names, comma-separated. Example: Hen, Chicken");
		TameIncludeSummons = Admin("5 - Travel", "TameIncludeSummons", value: false, "Only used when BringTames is on. Also take summoned creatures that follow you (skeletons, trolls and the like from staffs).");
		GateCompanionCargo = Admin("6 - Compatibility", "GateCompanionCargo", value: true, "When another mod takes a cart or tames through a portal with you, gate the cart's cargo and the tames' saddlebags (LoxSaddleBags, OdinHorse, or any container on a tame) like your own inventory. Covers a cart you're pulling, and tames from TeleportEverything or from Waypoints with 'Teleport Tames' on. (What [5 - Travel] brings along is always gated.)");
		WarnOnConflictingMods = Admin("6 - Compatibility", "WarnOnConflictingMods", value: true, "Log a warning at startup if another mod that changes item teleport rules is installed (or set to let every item through), and once per item if a tier-listed item is teleportable anyway.");
		AdminBypass = Admin("7 - Admin", "AdminBypass", value: false, "Server admins ignore the gate (useful for building/testing).");
		StatusCommand = Admin("7 - Admin", "StatusCommand", value: true, "Admin console command 'portalgate status': lists each tier as locked/unlocked for the world and for the calling player. Admin-only; shows boss names.");
		LogItemChecks = Admin("7 - Admin", "LogItemChecks", value: true, "At startup, check the item lists and log problems: names that aren't items in this version of Valheim, items listed in two tiers, and teleport-blocked items no tier lists (those wait for the final tier).");
		HintPositionSetting = Client("8 - Client", "HintPosition", HintPosition.Centered, "Where hints appear: Centered or TopLeft.");
		HintCooldownSeconds = Client("8 - Client", "HintCooldownSeconds", 5f, "Minimum seconds between repeat hints while standing in a portal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f));
		PortalLimitEnabled = Admin("9 - Portal Limit", "PortalLimitEnabled", value: true, "Limit how many portals the whole world can hold (everyone's together), raised as bosses are killed. Each tier's MaxPortals sets the limit once that tier's boss is dead. Every kind of portal counts. Only for new worlds: the first time the server loads a world with this on, a world that already has portals is left without a limit (so updating never locks anyone's portals). Admins can switch one world with the console command 'portalgate limit on' or 'off'. Portals are never removed.");
		PortalLimitStart = Admin("9 - Portal Limit", "PortalLimitStart", 2, "Only used when PortalLimitEnabled is on. Portals the world can hold before any tier's boss is killed. 0 = none.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000));
		PortalLimitAdminsExempt = Admin("9 - Portal Limit", "PortalLimitAdminsExempt", value: false, "Only used when PortalLimitEnabled is on. If true, server admins can build portals past the limit (their portals still count).");
		PortalLimitMessage = Admin("9 - Portal Limit", "PortalLimitMessage", "This world holds {count} of {limit} portals. A greater foe must fall before more can be built.", "Only used when PortalLimitEnabled is on. Shown when a portal can't be built. {count} = portals in the world, {limit} = the limit. Never name a boss.");
		PortalLimitLockWhenOver = Admin("9 - Portal Limit", "PortalLimitLockWhenOver", value: true, "Only used when PortalLimitEnabled is on. If true, while the world holds MORE portals than its limit, no portal works for anyone until enough are destroyed. Stops players squeezing in an extra portal by building two at the same moment. Careful: turning the limit on (or lowering it) in a world that's already over it locks every portal, and so does an admin building past the limit with PortalLimitAdminsExempt.");
		PortalLimitOverMessage = Admin("9 - Portal Limit", "PortalLimitOverMessage", "This world has too many portals ({count} of {limit}). Destroy one before any portal will work.", "Only used when PortalLimitEnabled is on. Shown at a portal while PortalLimitLockWhenOver locks it. {count}, {limit}. Never name a boss.");
		PortalLimitCountStonePortals = Admin("9 - Portal Limit", "PortalLimitCountStonePortals", value: true, "Only used when PortalLimitEnabled is on. true = Stone Portals follow the same limit as wood portals: they count, can't be built over the limit, and lock with the others. false = Stone Portals are left out of the limit entirely: they don't count, can always be built, and keep working while the world is over its limit. (Any portal that lets every item through counts as a Stone Portal here.)");
		for (int i = 0; i < TierDefaults.Length; i++)
		{
			string[] array = TierDefaults[i];
			string section = $"T{i} - {array[0]}";
			bool flag = i == TierDefaults.Length - 1;
			order = 0;
			tierEntries.Add(new TierEntries
			{
				Name = array[0],
				GlobalKey = Admin(section, "GlobalKey", array[1], "World kill flag for this tier's boss."),
				BossItem = Admin(section, "BossItem", array[2], "Boss drop (prefab name) the player must have held."),
				AllowEverything = (flag ? Admin(section, "AllowEverything", value: true, "Final tier: unlocks every teleport-blocked item, including unlisted ones (except NeverTeleport, and cheat-tier unless FinalTierIncludesCheatTier).") : null),
				Items = Admin(section, "Items", array[3], "Item prefab names this tier unlocks, comma-separated."),
				Hint = Admin(section, "Hint", array[4], flag ? "Hint while locked. Also used for any unlisted blocked item. Never name a boss." : "Hint while locked. Never name a boss."),
				UnlockMessage = Admin(section, "UnlockMessage", array[5], "Server-wide message when this tier's world key is first set. Never name a boss."),
				MaxPortals = Admin(section, "MaxPortals", TierMaxPortals[i], "Portals the whole world can hold once this tier's boss is killed (world key set), when PortalLimitEnabled is on. The highest unlocked tier's number counts. -1 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 1000))
			});
		}
	}

	public static void Parse()
	{
		List<Tier> list = new List<Tier>();
		for (int i = 0; i < Settings.tierEntries.Count; i++)
		{
			TierEntries tierEntries = Settings.tierEntries[i];
			list.Add(new Tier
			{
				Index = i,
				Name = tierEntries.Name,
				GlobalKey = tierEntries.GlobalKey.Value.Trim().ToLowerInvariant(),
				BossItem = tierEntries.BossItem.Value.Trim(),
				Items = SplitList(tierEntries.Items.Value),
				Hint = tierEntries.Hint.Value.Trim(),
				UnlockMessage = tierEntries.UnlockMessage.Value.Trim(),
				AllowEverything = (tierEntries.AllowEverything != null && tierEntries.AllowEverything.Value),
				MaxPortals = tierEntries.MaxPortals.Value
			});
		}
		NeverTeleportItems = SplitList(NeverTeleport.Value);
		TameAllow = new HashSet<string>(SplitList(TameAllowList.Value), StringComparer.OrdinalIgnoreCase);
		TameBlock = new HashSet<string>(SplitList(TameBlockList.Value), StringComparer.OrdinalIgnoreCase);
		foreach (KeyValuePair<string, int> hildirChestTier in HildirChestTiers)
		{
			if (HildirChestsByTier.Value)
			{
				list[hildirChestTier.Value].Items.Add(hildirChestTier.Key);
			}
			else
			{
				NeverTeleportItems.Add(hildirChestTier.Key);
			}
		}
		Dictionary<string, Tier> dictionary = new Dictionary<string, Tier>();
		foreach (Tier item in list)
		{
			foreach (string item2 in item.Items)
			{
				if (!dictionary.ContainsKey(item2))
				{
					dictionary[item2] = item;
				}
			}
		}
		TierOfItem = dictionary;
		Tiers = list;
		if (!((Object)(object)ObjectDB.instance != (Object)null))
		{
			return;
		}
		foreach (Tier item3 in list)
		{
			item3.BossItemToken = GetItemToken(item3.BossItem);
		}
	}

	public static void LogSummary()
	{
		List<string> list = new List<string>();
		list.Add($"Settings: Enabled={Enabled.Value}, UnlockWhen={UnlockWhen.Value}, Cumulative={CumulativeTiers.Value}, " + $"LockListedItemsAlways={LockListedItemsAlways.Value}, AdminBypass={AdminBypass.Value}, " + $"BringMount={BringMount.Value}, BringCart={BringCart.Value}, BringTames={BringTames.Value}, " + $"PortalLimit={PortalLimitEnabled.Value} (start {PortalLimitStart.Value}, admins exempt {PortalLimitAdminsExempt.Value}, " + $"lock when over {PortalLimitLockWhenOver.Value}, count stone portals {PortalLimitCountStonePortals.Value})");
		list.Add("NeverTeleport: " + Describe(NeverTeleportItems));
		list.Add("Tiers:");
		List<string> list2 = list;
		foreach (Tier tier in Tiers)
		{
			string text = ((tier.BossItemToken != null) ? (" (" + tier.BossItemToken + ")") : "");
			string arg = (tier.AllowEverything ? ("EVERYTHING" + ((tier.Items.Count > 0) ? (" + " + Describe(tier.Items)) : "")) : Describe(tier.Items));
			list2.Add($"  T{tier.Index} {tier.Name,-12} key={tier.GlobalKey}  bossItem={tier.BossItem}{text}  " + $"maxPortals={tier.MaxPortals}  items={arg}");
		}
		Logger.LogInfo((object)string.Join("\n", list2));
	}

	public static void Validate()
	{
		if ((Object)(object)ObjectDB.instance == (Object)null)
		{
			return;
		}
		foreach (Tier tier in Tiers)
		{
			tier.BossItemToken = GetItemToken(tier.BossItem);
		}
		if (!LogItemChecks.Value)
		{
			return;
		}
		int problems = 0;
		foreach (string neverTeleportItem in NeverTeleportItems)
		{
			Check(neverTeleportItem, "NeverTeleport");
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		foreach (Tier tier2 in Tiers)
		{
			string text = $"[T{tier2.Index} - {tier2.Name}]";
			if (string.IsNullOrEmpty(tier2.GlobalKey) && NeedsBossKill)
			{
				problems++;
				Logger.LogWarning((object)("LogItemChecks: " + text + " has no GlobalKey, so it never unlocks."));
			}
			if (string.IsNullOrEmpty(tier2.BossItem))
			{
				if (NeedsBossDrop)
				{
					problems++;
					Logger.LogWarning((object)("LogItemChecks: " + text + " has no BossItem, so it never unlocks."));
				}
			}
			else
			{
				Check(tier2.BossItem, text + " BossItem");
			}
			foreach (string item in tier2.Items)
			{
				Check(item, text + " Items");
				if (dictionary.TryGetValue(item, out var value))
				{
					problems++;
					Logger.LogWarning((object)$"LogItemChecks: '{item}' is listed in both T{value} and T{tier2.Index}.");
				}
				else
				{
					dictionary[item] = tier2.Index;
				}
			}
		}
		if (problems == 0)
		{
			Logger.LogInfo((object)"LogItemChecks: all item and boss-drop IDs exist.");
		}
		Hints.LogUntieredItems();
		void Check(string id, string where)
		{
			if (!((Object)(object)ObjectDB.instance.GetItemPrefab(id) != (Object)null))
			{
				problems++;
				Logger.LogWarning((object)("LogItemChecks: '" + id + "' in " + where + " is not an item in this version of Valheim."));
			}
		}
	}

	private static void Migrate()
	{
		if (HintFormat.Value == "An item blocks the portal. {hint}")
		{
			HintFormat.Value = "An item{where} blocks the portal. {hint}";
		}
		try
		{
			object obj = AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(config);
			Dictionary<ConfigDefinition, string> old = obj as Dictionary<ConfigDefinition, string>;
			if (old != null && old.Count != 0)
			{
				Copy("0.1 - General", "Enabled", (ConfigEntryBase)(object)Enabled);
				Copy("0.1 - General", "CumulativeTiers", (ConfigEntryBase)(object)CumulativeTiers);
				bool? flag = Flag("0.1 - General", "RequireWorldKey");
				bool? flag2 = Flag("0.1 - General", "RequirePlayerBossItem");
				if (flag2 == true)
				{
					UnlockWhen.Value = ((flag != false) ? UnlockRule.BossKilledAndDropHeld : UnlockRule.DropHeld);
				}
				else if (flag2 == false || flag.HasValue)
				{
					UnlockWhen.Value = UnlockRule.BossKilled;
				}
				Copy("0.2 - Item Rules", "NeverTeleport", (ConfigEntryBase)(object)NeverTeleport);
				Copy("0.2 - Item Rules", "HildirChestsByTier", (ConfigEntryBase)(object)HildirChestsByTier);
				Copy("0.2 - Item Rules", "GateListedVanillaItems", (ConfigEntryBase)(object)LockListedItemsAlways);
				Copy("0.2 - Item Rules", "FinalTierIncludesCheatTier", (ConfigEntryBase)(object)FinalTierIncludesCheatTier);
				bool? flag3 = Flag("0.2 - Item Rules", "LogUntieredItems");
				bool? flag4 = Flag("0.2 - Item Rules", "ValidateItemIds");
				if (flag3.HasValue || flag4.HasValue)
				{
					LogItemChecks.Value = flag3 != false || flag4 != false;
				}
				bool? flag5 = Flag("0.3 - Messages", "ShowUnlockHint");
				Copy("0.3 - Messages", "HintFormat", (ConfigEntryBase)(object)HintFormat);
				if (HintFormat.Value == "An item blocks the portal. {hint}")
				{
					HintFormat.Value = "An item{where} blocks the portal. {hint}";
				}
				if (flag5 == false)
				{
					HintFormat.Value = "";
				}
				Copy("0.3 - Messages", "AccurateTooltips", (ConfigEntryBase)(object)AccurateTooltips);
				Copy("0.3 - Messages", "AnnounceTierUnlock", (ConfigEntryBase)(object)AnnounceTierUnlock);
				Copy("0.3 - Messages", "HintPosition", (ConfigEntryBase)(object)HintPositionSetting);
				Copy("0.3 - Messages", "HintCooldownSeconds", (ConfigEntryBase)(object)HintCooldownSeconds);
				Copy("0.4 - Admin", "AdminBypass", (ConfigEntryBase)(object)AdminBypass);
				Copy("0.4 - Admin", "EnableStatusCommand", (ConfigEntryBase)(object)StatusCommand);
				Copy("0.5 - Compatibility", "RespectVanillaAllowAll", (ConfigEntryBase)(object)RespectVanillaAllowAll);
				Copy("0.5 - Compatibility", "WarnOnConflictingMods", (ConfigEntryBase)(object)WarnOnConflictingMods);
				bool? flag6 = Flag("0.5 - Compatibility", "GateAttachedCartCargo");
				bool? flag7 = Flag("0.5 - Compatibility", "GateTameCargo");
				if (flag6.HasValue || flag7.HasValue)
				{
					GateCompanionCargo.Value = flag6 != false || flag7 != false;
				}
				Take("0.6 - XPortal Compatibility", "LogPortalModDetection");
			}
			void Copy(string section, string key, ConfigEntryBase to)
			{
				string text = Take(section, key);
				if (text != null)
				{
					to.SetSerializedValue(text);
				}
			}
			bool? Flag(string section, string key)
			{
				if (!bool.TryParse(Take(section, key), out var result))
				{
					return null;
				}
				return result;
			}
			string Take(string section, string key)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Expected O, but got Unknown
				ConfigDefinition key2 = new ConfigDefinition(section, key);
				if (!old.TryGetValue(key2, out var value))
				{
					return null;
				}
				old.Remove(key2);
				return value;
			}
		}
		catch (Exception ex)
		{
			Logger.LogWarning((object)("Couldn't carry settings over from an older version; check the config. " + ex.Message));
		}
	}

	private static string GetItemToken(string prefab)
	{
		if (string.IsNullOrEmpty(prefab))
		{
			return null;
		}
		GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
		if (!Object.op_Implicit((Object)(object)itemPrefab))
		{
			return null;
		}
		ItemDrop component = itemPrefab.GetComponent<ItemDrop>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			return null;
		}
		return component.m_itemData.m_shared.m_name;
	}

	private static HashSet<string> SplitList(string value)
	{
		return new HashSet<string>(from s in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim() into s
			where s.Length > 0
			select s);
	}

	private static string Describe(HashSet<string> set)
	{
		if (set.Count != 0)
		{
			return string.Join(", ", set);
		}
		return "(none)";
	}

	private static ConfigEntry<T> Admin<T>(string section, string key, T value, string description, AcceptableValueBase range = null)
	{
		return Bind(section, key, value, description, range, adminOnly: true);
	}

	private static ConfigEntry<T> Client<T>(string section, string key, T value, string description, AcceptableValueBase range = null)
	{
		return Bind(section, key, value, description, range, adminOnly: false);
	}

	private static ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range, bool adminOnly)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Expected O, but got Unknown
		ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
		{
			IsAdminOnly = adminOnly,
			Order = (order -= 1)
		};
		if (!adminOnly)
		{
			description += " (Not synced.)";
		}
		return config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { val }));
	}
}
internal class StatusCommand : ConsoleCommand
{
	public override string Name => "portalgate";

	public override string Help => "portalgate status - BossGatedPortals tiers for the world and for you; portalgate limit on|off - whether the portal limit applies to this world (admin only)";

	public override List<string> CommandOptionList()
	{
		return new List<string> { "status", "limit" };
	}

	public override void Run(string[] args, Terminal context)
	{
		string obj = ((args.Length != 0) ? args[0].ToLowerInvariant() : "");
		string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
		bool flag = obj == "limit" && (text == "on" || text == "off");
		if (obj != "status" && !flag)
		{
			context.AddString("Usage: portalgate status | portalgate limit on|off");
			return;
		}
		if (!flag && !Settings.StatusCommand.Value)
		{
			context.AddString("portalgate status is turned off (StatusCommand).");
			return;
		}
		if (!SynchronizationManager.Instance.PlayerIsAdmin)
		{
			context.AddString("portalgate is for server admins only.");
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
		{
			context.AddString("Join a world first.");
			return;
		}
		if (flag)
		{
			PortalLimit.SetWorldChoice(text == "on");
			context.AddString("Portal limit for this world set to " + text + "." + (Settings.PortalLimitEnabled.Value ? "" : " (PortalLimitEnabled is off in the config, so nothing is limited.)"));
			return;
		}
		context.AddString(string.Format("BossGatedPortals {0} - Enabled={1}, ", "1.3.3", Settings.Enabled.Value) + $"UnlockWhen={Settings.UnlockWhen.Value}, " + $"Cumulative={Settings.CumulativeTiers.Value}, AdminBypass={Settings.AdminBypass.Value}");
		foreach (Tier tier in Settings.Tiers)
		{
			bool flag2 = !string.IsNullOrEmpty(tier.GlobalKey) && ZoneSystem.instance.GetGlobalKey(tier.GlobalKey);
			bool flag3 = !string.IsNullOrEmpty(tier.BossItemToken) && localPlayer.IsMaterialKnown(tier.BossItemToken);
			bool flag4 = Gate.IsTierUnlocked(localPlayer, tier);
			context.AddString(string.Format("T{0} {1}: world {2} ({3}), ", tier.Index, tier.Name, flag2 ? "UNLOCKED" : "locked", tier.GlobalKey) + "you " + (flag3 ? "have" : "have not") + " held " + tier.BossItem + " -> " + (flag4 ? "UNLOCKED" : "LOCKED") + " for you" + $" (MaxPortals {tier.MaxPortals})");
		}
		int num = PortalLimit.Limit();
		int num2 = PortalLimit.Count();
		bool? flag5 = PortalLimit.WorldChoice();
		context.AddString("Portal limit " + (Settings.PortalLimitEnabled.Value ? "ON" : "off") + " in the config, " + ((!flag5.HasValue) ? "not decided yet" : ((flag5 == true) ? "ON" : "off")) + " for this world ('portalgate limit on|off'): " + ((num2 < 0) ? "?" : num2.ToString()) + " portals in the world, limit " + ((num < 0) ? "none" : num.ToString()));
	}
}
internal static class Tames
{
	public struct Bag
	{
		public Inventory Inventory;

		public string Where;
	}

	public const string TeleportEverythingGUID = "zenox.teleporteverything";

	public const string WaypointsGUID = "RustyMods.Waypoints";

	private const string OdinHorseContainerName = "SaddlebagContainer";

	private static readonly int OdinHorseBagsKey = StringExtensionMethods.GetStableHashCode("rae_saddlebags");

	internal static int waypointsCheckDepth;

	private static bool teResolved;

	private static ConfigEntryBase teEnabled;

	private static Func<Vector3, GameObject, bool, List<Character>> teAllies;

	private static FieldInfo tePortalPosition;

	private static bool forkResolved;

	private static ConfigEntryBase forkEnabled;

	private static Action forkSetIncludeMode;

	private static Func<Character, bool> forkIsValidAlly;

	private static bool waypointsResolved;

	private static ConfigEntryBase waypointsTeleportTames;

	private static readonly List<Bag> cargo = new List<Bag>();

	private static readonly HashSet<Character> seen = new HashSet<Character>();

	private static readonly Dictionary<ZDOID, KeyValuePair<string, Inventory>> savedBags = new Dictionary<ZDOID, KeyValuePair<string, Inventory>>();

	public static List<Bag> Cargo(Player player)
	{
		//IL_0123: 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_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: Unknown result type (might be due to invalid IL or missing references)
		cargo.Clear();
		seen.Clear();
		try
		{
			if (Travel.atPortal && Travel.BringsMount)
			{
				AddBags(Travel.RiddenMount(player));
				Vagon val = Travel.TowedCart(player);
				if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.m_container))
				{
					cargo.Add(new Bag
					{
						Inventory = val.m_container.GetInventory(),
						Where = " in " + Travel.RiddenMount(player).GetHoverName() + "'s " + val.GetHoverName()
					});
				}
			}
			if (Travel.atPortal && Travel.BringsTames)
			{
				foreach (Character item in Travel.SelectTames(player))
				{
					AddBags(item);
				}
			}
			if (!Settings.GateCompanionCargo.Value)
			{
				return cargo;
			}
			if (TeleportEverythingTakesTames())
			{
				Vector3 arg = (Vector3)(((??)(tePortalPosition.GetValue(null) as Vector3?)) ?? ((Component)player).transform.position);
				foreach (Character item2 in teAllies(arg, ((Component)player).gameObject, arg3: false))
				{
					AddBags(item2);
				}
			}
			if (ForkTakesTames())
			{
				forkSetIncludeMode();
				foreach (Character allCharacter in Character.GetAllCharacters())
				{
					if (Object.op_Implicit((Object)(object)allCharacter) && !allCharacter.IsPlayer() && forkIsValidAlly(allCharacter))
					{
						AddBags(allCharacter);
					}
				}
			}
			if (waypointsCheckDepth > 0 && WaypointsTakesTames())
			{
				foreach (Character allCharacter2 in Character.GetAllCharacters())
				{
					MonsterAI component = ((Component)allCharacter2).GetComponent<MonsterAI>();
					if (Object.op_Implicit((Object)(object)((Component)allCharacter2).GetComponent<Tameable>()) && Object.op_Implicit((Object)(object)component) && (Object)(object)component.GetFollowTarget() == (Object)(object)((Component)player).gameObject)
					{
						AddBags(allCharacter2);
					}
				}
			}
		}
		catch (Exception e)
		{
			Failsafe.Report("Tame saddlebag check", e);
			cargo.Clear();
		}
		return cargo;
	}

	private static void AddBags(Character tame)
	{
		if (!Object.op_Implicit((Object)(object)tame) || !seen.Add(tame))
		{
			return;
		}
		string text = " in " + tame.GetHoverName() + "'s saddlebags";
		Container[] componentsInChildren = ((Component)tame).GetComponentsInChildren<Container>(true);
		foreach (Container val in componentsInChildren)
		{
			Inventory inventory = val.GetInventory();
			if (inventory != null && ((Object)((Component)val).gameObject).name != "SaddlebagContainer")
			{
				cargo.Add(new Bag
				{
					Inventory = inventory,
					Where = text
				});
			}
		}
		ZNetView component = ((Component)tame).GetComponent<ZNetView>();
		if (Object.op_Implicit((Object)(object)component) && component.IsValid())
		{
			Inventory val2 = SavedBags(component.GetZDO());
			if (val2 != null)
			{
				cargo.Add(new Bag
				{
					Inventory = val2,
					Where = text
				});
			}
		}
	}

	private static Inventory SavedBags(ZDO zdo)
	{
		//IL_0021: 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_0056: Expected O, but got Unknown
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Expected O, but got Unknown
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		string text = zdo.GetString(OdinHorseBagsKey, "");
		if (string.IsNullOrEmpty(text))
		{
			return null;
		}
		if (savedBags.TryGetValue(zdo.m_uid, out var value) && value.Key == text)
		{
			return value.Value;
		}
		Inventory val = new Inventory("Saddlebags", (Sprite)null, 32, 32);
		val.Load(new ZPackage(text));
		savedBags[zdo.m_uid] = new KeyValuePair<string, Inventory>(text, val);
		return val;
	}

	private static bool ForkTakesTames()
	{
		if (!forkResolved)
		{
			forkResolved = true;
			bool installed;
			Type type = PluginType("com.kpro.TeleportEverything", "TeleportEverything.Plugin", out installed);
			if (installed)
			{
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "SetIncludeMode", Type.EmptyTypes, (Type[])null));
				MethodInfo methodInfo2 = ((type == null) ? null : AccessTools.Method(type, "IsValidAlly", new Type[1] { typeof(Character) }, (Type[])null));
				forkEnabled = Compatibility.FindSetting("com.kpro.TeleportEverything", "--- Mod ---", "Enable Mod");
				if (methodInfo != null && methodInfo.IsStatic && methodInfo2 != null && methodInfo2.IsStatic && methodInfo2.ReturnType == typeof(bool) && forkEnabled != null)
				{
					forkSetIncludeMode = AccessTools.MethodDelegate<Action>(methodInfo, (object)null, true);
					forkIsValidAlly = AccessTools.MethodDelegate<Func<Character, bool>>(methodInfo2, (object)null, true);
				}
				else
				{
					Logger.LogWarning((object)"GateCompanionCargo: the TeleportEverything fork's tame code changed (update?). Saddlebags on tames it teleports aren't gated.");
				}
			}
		}
		if (forkIsValidAlly != null)
		{
			return Compatibility.IsSet(forkEnabled, "True");
		}
		return false;
	}

	private static bool TeleportEverythingTakesTames()
	{
		if (!teResolved)
		{
			teResolved = true;
			bool installed;
			Type type = PluginType("zenox.teleporteverything", "TeleportEverything.TeleportEverythingPlugin", out installed);
			if (installed)
			{
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "GetTransportableAllies", new Type[3]
				{
					typeof(Vector3),
					typeof(GameObject),
					typeof(bool)
				}, (Type[])null));
				tePortalPosition = ((type == null) ? null : AccessTools.Field(type, "activePortalPosition"));
				teEnabled = Compatibility.FindSetting("zenox.teleporteverything", "General", "Enabled");
				if (methodInfo != null && methodInfo.ReturnType == typeof(List<Character>) && tePortalPosition?.FieldType == typeof(Vector3?) && teEnabled != null)
				{
					teAllies = AccessTools.MethodDelegate<Func<Vector3, GameObject, bool, List<Character>>>(methodInfo, (object)null, true);
				}
				else
				{
					Logger.LogWarning((object)"GateCompanionCargo: TeleportEverything's tame code changed (TeleportEverything update?). Saddlebags on tames it teleports aren't gated.");
				}
			}
		}
		if (teAllies != null)
		{
			return Compatibility.IsSet(teEnabled, "True");
		}
		return false;
	}

	private static bool WaypointsTakesTames()
	{
		if (!waypointsResolved)
		{
			waypointsResolved = true;
			waypointsTeleportTames = Compatibility.FindSetting("RustyMods.Waypoints", "2 - Settings", "5 - Teleport Tames");
			if (waypointsTeleportTames == null)
			{
				Logger.LogWarning((object)"GateCompanionCargo: Waypoints' 'Teleport Tames' setting wasn't found (Waypoints update?). Saddlebags on tames it teleports aren't gated.");
			}
		}
		return Compatibility.IsSet(waypointsTeleportTames, "On");
	}

	internal static Type PluginType(string guid, string typeName, out bool installed)
	{
		installed = Chainloader.PluginInfos.TryGetValue(guid, out var value) && (Object)(object)value.Instance != (Object)null;
		if (!installed)
		{
			return null;
		}
		return ((object)value.Instance).GetType().Assembly.GetType(typeName);
	}
}
[HarmonyPatch]
internal static class WaypointsCanTeleportPatch
{
	private static bool resolved;

	private static MethodBase target;

	private static bool Prepare()
	{
		if (resolved)
		{
			return target != null;
		}
		resolved = true;
		bool installed;
		Type type = Tames.PluginType("RustyMods.Waypoints", "Waypoints.Behaviors.Waypoint", out installed);
		target = ((type == null) ? null : AccessTools.Method(type, "CanTeleport", new Type[2]
		{
			typeof(Player),
			typeof(bool)
		}, (Type[])null));
		if (installed && target == null)
		{
			Logger.LogWarning((object)"GateCompanionCargo: Waypoints' item check wasn't found (Waypoints update?). Saddlebags on tames it teleports aren't gated.");
		}
		return target != null;
	}

	private static MethodBase TargetMethod()
	{
		return target;
	}

	private static void Prefix()
	{
		Tames.waypointsCheckDepth++;
	}

	private static void Finalizer()
	{
		Tames.waypointsCheckDepth--;
	}
}
internal static class TargetPortalPins
{
	private static bool resolved;

	internal static MethodBase lookup;

	private static FieldInfo activePins;

	private static FieldInfo teleporting;

	internal static bool inLookup;

	internal static bool Resolve()
	{
		if (resolved)
		{
			return lookup != null;
		}
		resolved = true;
		bool installed;
		Type type = Tames.PluginType("org.bepinex.plugins.targetportal", "TargetPortal.Map", out installed);
		if (type == null)
		{
			return false;
		}
		activePins = AccessTools.Field(type, "activePins");
		teleporting = AccessTools.Field(type, "Teleporting");
		MethodBase methodBase = AccessTools.Method(type, "GetClosestPortal", (Type[])null, (Type[])null);
		if (methodBase != null && activePins?.FieldType == typeof(Dictionary<PinData, ZDO>) && teleporting?.FieldType == typeof(bool))
		{
			lookup = methodBase;
		}
		else if (installed)
		{
			Logger.LogWarning((object)"TargetPortal's portal map changed (TargetPortal update?). Its portal icons are no longer kept clickable under other map pins.");
		}
		return lookup != null;
	}

	private static Dictionary<PinData, ZDO> Pins()
	{
		return (Dictionary<PinData, ZDO>)activePins.GetValue(null);
	}

	private static bool Shown(PinData pin)
	{
		if (Object.op_Implicit((Object)(object)pin.m_uiElement))
		{
			return ((Component)pin.m_uiElement).gameObject.activeInHierarchy;
		}
		return false;
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	internal static void PreferPortal(Vector3 pos, float radius, bool mustBeVisible, ref PinData result)
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<PinData, ZDO> dictionary = Pins();
		if (dictionary == null || (result != null && dictionary.ContainsKey(result)))
		{
			return;
		}
		PinData val = null;
		float num = radius;
		foreach (PinData key in dictionary.Keys)
		{
			if (!mustBeVisible || Shown(key))
			{
				float num2 = Utils.DistanceXZ(pos, key.m_pos);
				if (num2 < num)
				{
					val = key;
					num = num2;
				}
			}
		}
		if (val != null)
		{
			result = val;
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	internal static void BringToFront()
	{
		if (!(bool)teleporting.GetValue(null))
		{
			return;
		}
		Dictionary<PinData, ZDO> dictionary = Pins();
		if (dictionary == null)
		{
			return;
		}
		int num = 0;
		foreach (PinData key in dictionary.Keys)
		{
			if (Shown(key))
			{
				num++;
			}
		}
		foreach (PinData key2 in dictionary.Keys)
		{
			if (Shown(key2))
			{
				RectTransform uiElement = key2.m_uiElement;
				if (((Transform)uiElement).GetSiblingIndex() < ((Transform)uiElement).parent.childCount - num)
				{
					((Transform)uiElement).SetAsLastSibling();
				}
			}
		}
	}
}
[HarmonyPatch]
internal static class TargetPortalLookupPatch
{
	private static bool Prepare()
	{
		return TargetPortalPins.Resolve();
	}

	private static MethodBase TargetMethod()
	{
		return TargetPortalPins.lookup;
	}

	private static void Prefix()
	{
		TargetPortalPins.inLookup = true;
	}

	private static void Finalizer()
	{
		TargetPortalPins.inLookup = false;
	}
}
[HarmonyPatch(typeof(Minimap), "GetClosestPin", new Type[]
{
	typeof(Vector3),
	typeof(float),
	typeof(bool)
})]
internal static class ClosestPinPatch
{
	private static bool Prepare()
	{
		return TargetPortalPins.Resolve();
	}

	[HarmonyPriority(0)]
	private static void Postfix(Vector3 pos, float radius, bool mustBeVisible, ref PinData __result)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		if (!TargetPortalPins.inLookup)
		{
			return;
		}
		try
		{
			TargetPortalPins.PreferPortal(pos, radius, mustBeVisible, ref __result);
		}
		catch (Exception e)
		{
			Failsafe.Report("TargetPortal portal icon click", e);
		}
	}
}
[HarmonyPatch(typeof(Minimap), "UpdatePins")]
internal static class PortalIconsOnTopPatch
{
	private static bool Prepare()
	{
		return TargetPortalPins.Resolve();
	}

	private static void Postfix()
	{
		try
		{
			TargetPortalPins.BringToFront();
		}
		catch (Exception e)
		{
			Failsafe.Report("TargetPortal portal icons on top", e);
		}
	}
}
internal class Tier
{
	public int Index;

	public string Name;

	public string GlobalKey;

	public string BossItem;

	public string BossItemToken;

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

	public string Hint;

	public string UnlockMessage;

	public bool AllowEverything;

	public int MaxPortals;
}
internal static class Tooltips
{
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(bool),
		typeof(float),
		typeof(int),
		typeof(bool)
	})]
	private static class GetTooltipPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Expected O, but got Unknown
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Expected O, but got Unknown
			FieldInfo fieldInfo = AccessTools.Field(typeof(ItemData), "m_shared");
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(SharedData), "m_teleportable");
			MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "GetGlobalKey", new Type[1] { typeof(GlobalKeys) }, (Type[])null);
			CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null).MatchStartForward((CodeMatch[])(object)new CodeMatch[10]
			{
				new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo2, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Ldc_I4 || i.opcode == OpCodes.Ldc_I4_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)methodInfo, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"\n<color=orange>$item_noteleport</color>", (string)null)
			});
			if (val.IsInvalid)
			{
				Logger.LogWarning((object)"AccurateTooltips: couldn't find the teleport line in the item tooltip (game update?). Tooltips will show vanilla text.");
				return instructions;
			}
			object operand = val.InstructionAt(3).operand;
			val.Advance(1).RemoveInstructions(7).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Tooltips), "ShowNoTeleport", (Type[])null, (Type[])null)),
				new CodeInstruction(OpCodes.Brfalse, operand)
			});
			return val.InstructionEnumeration();
		}
	}

	[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
	private static class InventoryGridIconPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Expected O, but got Unknown
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Expected O, but got Unknown
			FieldInfo fieldInfo = AccessTools.Field(typeof(InventoryElement), "m_noteleport");
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(ItemData), "m_shared");
			FieldInfo fieldInfo3 = AccessTools.Field(typeof(SharedData), "m_teleportable");
			MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "GetGlobalKey", new Type[1] { typeof(GlobalKeys) }, (Type[])null);
			CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null).MatchStartForward((CodeMatch[])(object)new CodeMatch[13]
			{
				new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => CodeInstructionExtensions.IsLdloc(i, (LocalBuilder)null)), (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo2, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldfld, (object)fieldInfo3, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Brtrue || i.opcode == OpCodes.Brtrue_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Ldc_I4 || i.opcode == OpCodes.Ldc_I4_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)methodInfo, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldc_I4_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ceq, (object)null, (string)null),
				new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Br || i.opcode == OpCodes.Br_S), (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldc_I4_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)null, (string)null)
			});
			if (val.IsInvalid)
			{
				Logger.LogWarning((object)"AccurateTooltips: couldn't find the no-teleport icon in the inventory grid (game update?). Slot icons will show vanilla state.");
				return instructions;
			}
			val.Advance(2).RemoveInstructions(10).Insert((CodeInstruction[])(object)new CodeInstruction[1]
			{
				new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Tooltips), "ShowNoTeleport", (Type[])null, (Type[])null))
			});
			return val.InstructionEnumeration();
		}
	}

	public static bool ShowNoTeleport(ItemData item)
	{
		try
		{
			return Decide(item);
		}
		catch (Exception e)
		{
			Failsafe.Report("Teleport tooltip/icon", e);
			try
			{
				return Vanilla(item);
			}
			catch
			{
				return false;
			}
		}
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static bool Decide(ItemData item)
	{
		Player localPlayer = Player.m_localPlayer;
		if (!Settings.Enabled.Value || !Settings.AccurateTooltips.Value || (Object)(object)localPlayer == (Object)null)
		{
			return Vanilla(item);
		}
		return !Gate.CanTeleport(localPlayer, item);
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static bool Vanilla(ItemData item)
	{
		if (!item.m_shared.m_teleportable)
		{
			return !Failsafe.TeleportAllSet();
		}
		return false;
	}
}
internal static class Travel
{
	public struct Trip
	{
		public Character Mount;

		public Vagon Cart;

		public List<Character> Tames;

		public Vector3 Exit;

		public Quaternion ExitRotation;

		public bool CartOnMount;

		public Vector3 CartOffset;

		public Quaternion CartTurn;

		public Vagon LeaveCart;
	}

	public const string AdjustablePortalsGUID = "MidnightsFX.AdjustablePortals";

	public const string TeleportEverythingForkGUID = "com.kpro.TeleportEverything";

	private const float ArrivalTimeoutSeconds = 20f;

	private const float UnhitchTimeoutSeconds = 1f;

	private const float LandingTimeoutSeconds = 3f;

	private static readonly float[] SlotAngles = new float[4] { 45f, -45f, 90f, -90f };

	private const float RingSpacing = 2.5f;

	private const float CartParkDistance = 4f;

	private const int FloorSearchHeight = 3;

	private const float MaxStepHeight = 1f;

	private static int solidMask;

	internal static bool atPortal;

	private static bool resolved;

	private static string adjustablePortals;

	private static string teleportEverything;

	private static ConfigEntryBase teleportEverythingOn;

	private static string teleportEverythingFork;

	private static ConfigEntryB