Decompiled source of QuickTeleport v1.1.0

plugins/QuickTeleport/QuickTeleport.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Muindor.ServerConfig;
using QuickTeleport.Teleport;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Egor Fadeev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Faster portal and dungeon teleports for Valheim: end the teleport as soon as the screen is black and the destination is loaded, or run the vanilla timing N times faster.")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+be4ea95e7c7c23e6b4cf630ceb5d310166dc0954")]
[assembly: AssemblyProduct("Quick Teleport")]
[assembly: AssemblyTitle("QuickTeleport")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Muindor.ServerConfig
{
	public static class ConfigChannel
	{
		private static ConfigSync? _sync;

		private static ManualLogSource? _log;

		private static string _rpcName = string.Empty;

		public static void Activate(ConfigSync sync, ManualLogSource log)
		{
			_sync = sync;
			_log = log;
			_rpcName = sync.PluginGuid + ".ServerConfig";
			sync.Changed += Broadcast;
		}

		internal static void OnNewConnection(ZNet net, ZNetPeer peer)
		{
			if (_sync != null && !net.IsServer() && peer != null && peer.m_server && peer.m_rpc != null)
			{
				_sync.Clear();
				peer.m_rpc.Register<string>(_rpcName, (Action<ZRpc, string>)Receive);
			}
		}

		internal static void OnPeerInfo(ZNet net, ZRpc rpc)
		{
			if (_sync == null || !net.IsServer())
			{
				return;
			}
			ZNetPeer peer = net.GetPeer(rpc);
			if (peer != null && peer.IsReady() && peer.m_rpc != null)
			{
				peer.m_rpc.Invoke(_rpcName, new object[1] { _sync.BuildPayload().Serialize() });
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogInfo((object)("Sent the configuration to " + peer.m_playerName + " (ConfigPriority " + (_sync.Priority.Value ? "on" : "off") + ")"));
				}
			}
		}

		internal static void OnSessionEnd()
		{
			_sync?.Clear();
		}

		internal static void PatchFailed(string patch, Exception exception)
		{
			ManualLogSource? log = _log;
			if (log != null)
			{
				log.LogError((object)$"{patch} failed, the configuration channel may be inactive: {exception}");
			}
		}

		private static void Broadcast()
		{
			ZNet instance = ZNet.instance;
			if (_sync == null || (Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			string text = _sync.BuildPayload().Serialize();
			int num = 0;
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer.IsReady() && peer.m_rpc != null)
				{
					peer.m_rpc.Invoke(_rpcName, new object[1] { text });
					num++;
				}
			}
			if (num > 0)
			{
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"A setting changed; sent the configuration to {num} player(s)");
				}
			}
		}

		private static void Receive(ZRpc rpc, string text)
		{
			if (_sync == null)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ConfigPayload payload;
			string error;
			if ((Object)(object)instance != (Object)null && instance.IsServer())
			{
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogWarning((object)"Ignored a configuration sent to this server by a client");
				}
			}
			else if (!ConfigPayload.TryParse(text, out payload, out error))
			{
				ManualLogSource? log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Ignored the configuration from the server, keeping the local settings: " + error));
				}
			}
			else
			{
				_sync.Apply(payload);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class ZNet_OnNewConnection_Patch
	{
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			try
			{
				ConfigChannel.OnNewConnection(__instance, peer);
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("OnNewConnection", exception);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class ZNet_RPC_PeerInfo_Patch
	{
		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			try
			{
				ConfigChannel.OnPeerInfo(__instance, rpc);
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("RPC_PeerInfo", exception);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class ZNet_OnDestroy_Patch
	{
		private static void Postfix()
		{
			try
			{
				ConfigChannel.OnSessionEnd();
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("OnDestroy", exception);
			}
		}
	}
	public sealed class ConfigPayload
	{
		public const string Header = "muindor.config/1";

		public string ModVersion { get; }

		public bool Priority { get; }

		public IList<KeyValuePair<string, string>> Values { get; }

		public ConfigPayload(string modVersion, bool priority, IEnumerable<KeyValuePair<string, string>>? values = null)
		{
			ModVersion = modVersion ?? string.Empty;
			Priority = priority;
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			if (values != null)
			{
				list.AddRange(values);
			}
			Values = list;
		}

		public string Serialize()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("muindor.config/1").Append('\n');
			stringBuilder.Append(Escape(ModVersion, escapeEquals: false)).Append('\n');
			stringBuilder.Append(Priority ? "true" : "false").Append('\n');
			foreach (KeyValuePair<string, string> value in Values)
			{
				stringBuilder.Append(Escape(value.Key, escapeEquals: true)).Append('=').Append(Escape(value.Value ?? string.Empty, escapeEquals: false))
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static bool TryParse(string? text, out ConfigPayload? payload, out string error)
		{
			payload = null;
			if (string.IsNullOrEmpty(text))
			{
				error = "the payload is empty";
				return false;
			}
			string[] array = text.Split(new char[1] { '\n' });
			if (array.Length < 3)
			{
				error = "the payload is truncated";
				return false;
			}
			if (Trim(array[0]) != "muindor.config/1")
			{
				error = "unknown format \"" + Trim(array[0]) + "\", expected \"muindor.config/1\"";
				return false;
			}
			string modVersion = Unescape(Trim(array[1]));
			string text2 = Trim(array[2]);
			if (text2 != "true" && text2 != "false")
			{
				error = "\"" + text2 + "\" is not a valid priority flag";
				return false;
			}
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			for (int i = 3; i < array.Length; i++)
			{
				string text3 = Trim(array[i]);
				if (text3.Length != 0)
				{
					int num = IndexOfSeparator(text3);
					if (num < 0)
					{
						error = $"line {i + 1} has no key/value separator";
						return false;
					}
					string text4 = Unescape(text3.Substring(0, num));
					if (text4.Length == 0)
					{
						error = $"line {i + 1} has an empty key";
						return false;
					}
					list.Add(new KeyValuePair<string, string>(text4, Unescape(text3.Substring(num + 1))));
				}
			}
			payload = new ConfigPayload(modVersion, text2 == "true", list);
			error = string.Empty;
			return true;
		}

		private static string Trim(string line)
		{
			if (!line.EndsWith("\r", StringComparison.Ordinal))
			{
				return line;
			}
			return line.Substring(0, line.Length - 1);
		}

		private static int IndexOfSeparator(string line)
		{
			for (int i = 0; i < line.Length; i++)
			{
				if (line[i] == '\\')
				{
					i++;
				}
				else if (line[i] == '=')
				{
					return i;
				}
			}
			return -1;
		}

		private static string Escape(string value, bool escapeEquals)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '=':
					if (escapeEquals)
					{
						stringBuilder.Append("\\=");
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			return stringBuilder.ToString();
		}

		private static string Unescape(string value)
		{
			if (value.IndexOf('\\') < 0)
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			for (int i = 0; i < value.Length; i++)
			{
				if (value[i] != '\\' || i + 1 >= value.Length)
				{
					stringBuilder.Append(value[i]);
					continue;
				}
				char c = value[++i];
				switch (c)
				{
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class ConfigSync
	{
		private const string SyncedNote = "[synced] ";

		private readonly Dictionary<string, ISyncedEntry> _byKey = new Dictionary<string, ISyncedEntry>(StringComparer.Ordinal);

		private readonly List<ISyncedEntry> _entries = new List<ISyncedEntry>();

		private readonly HashSet<ConfigEntryBase> _watched = new HashSet<ConfigEntryBase>();

		private readonly ConfigFile _config;

		private readonly ManualLogSource _log;

		public string PluginGuid { get; }

		public string ModName { get; }

		public string ModVersion { get; }

		public ConfigEntry<bool> Priority { get; }

		public bool Locked { get; private set; }

		public string? ServerVersion { get; private set; }

		public int LockedCount { get; private set; }

		public event Action? Changed;

		public ConfigSync(ConfigFile config, string pluginGuid, string modName, string modVersion, ManualLogSource log)
		{
			_config = config;
			_log = log;
			PluginGuid = pluginGuid;
			ModName = modName;
			ModVersion = modVersion;
			Priority = config.Bind<bool>("Server", "ConfigPriority", false, "Server only, and off by default. Turn it on in the configuration of a dedicated server, or of the player hosting the session, to have every client that also has the mod run on the settings marked [synced] below instead of its own. The clients' own config files are never touched: their settings come back the moment they disconnect. This never makes the mod required on either side - players without it are unaffected, and a player with it can still join a server that does not have it.");
			_watched.Add((ConfigEntryBase)(object)Priority);
			config.SettingChanged += OnSettingChanged;
		}

		public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			ConfigDescription val = new ConfigDescription("[synced] " + description.Description, description.AcceptableValues, description.Tags);
			ConfigEntry<T> val2 = _config.Bind<T>(section, key, defaultValue, val);
			SyncedEntry<T> syncedEntry = new SyncedEntry<T>(val2);
			_byKey.Add(syncedEntry.Key, syncedEntry);
			_entries.Add(syncedEntry);
			_watched.Add((ConfigEntryBase)(object)val2);
			return syncedEntry;
		}

		public ConfigPayload BuildPayload()
		{
			bool value = Priority.Value;
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(value ? _entries.Count : 0);
			if (value)
			{
				foreach (ISyncedEntry entry in _entries)
				{
					try
					{
						list.Add(new KeyValuePair<string, string>(entry.Key, entry.LocalAsString()));
					}
					catch (Exception ex)
					{
						_log.LogWarning((object)(entry.Key + " could not be sent to the clients: " + ex.Message));
					}
				}
			}
			return new ConfigPayload(ModVersion, value, list);
		}

		public void Apply(ConfigPayload payload)
		{
			ClearValues();
			if (!payload.Priority)
			{
				Locked = false;
				ServerVersion = null;
				LockedCount = 0;
				_log.LogInfo((object)("The server runs " + ModName + " " + payload.ModVersion + " with ConfigPriority off; keeping the local settings"));
				return;
			}
			int num = 0;
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, string> value2 in payload.Values)
			{
				string error;
				if (!_byKey.TryGetValue(value2.Key, out ISyncedEntry value))
				{
					list.Add(value2.Key);
				}
				else if (value.ApplyServerValue(value2.Value, out error))
				{
					num++;
				}
				else
				{
					_log.LogWarning((object)("Keeping the local " + value2.Key + ": the server sent a value this version cannot read, " + error));
				}
			}
			Locked = true;
			ServerVersion = payload.ModVersion;
			LockedCount = num;
			int num2 = _entries.Count - num;
			string text = ((list.Count > 0) ? string.Format(", {0} unknown here ({1})", list.Count, string.Join(", ", list.ToArray())) : string.Empty);
			string text2 = ((num2 > 0) ? $", {num2} kept local" : string.Empty);
			_log.LogInfo((object)$"The server ({ModName} {payload.ModVersion}) decides the settings: {num} applied{text2}{text}");
		}

		public void Clear()
		{
			if (Locked || LockedCount != 0)
			{
				ClearValues();
				Locked = false;
				ServerVersion = null;
				LockedCount = 0;
				_log.LogInfo((object)"Back to the local settings");
			}
		}

		public string Describe()
		{
			if (!Locked)
			{
				return "local settings";
			}
			return $"settings from the server ({ModName} {ServerVersion}, {LockedCount} values)";
		}

		private void ClearValues()
		{
			foreach (ISyncedEntry entry in _entries)
			{
				entry.ClearServerValue();
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			if (this.Changed != null)
			{
				ConfigEntryBase changedSetting = args.ChangedSetting;
				if (changedSetting != null && _watched.Contains(changedSetting))
				{
					this.Changed();
				}
			}
		}
	}
	public interface ISyncedEntry
	{
		string Key { get; }

		string LocalAsString();

		bool ApplyServerValue(string text, out string error);

		void ClearServerValue();
	}
	public sealed class SyncedEntry<T> : ISyncedEntry
	{
		private T _serverValue;

		private bool _hasServerValue;

		public ConfigEntry<T> Local { get; }

		public string Key { get; }

		public T Value
		{
			get
			{
				if (!_hasServerValue)
				{
					return Local.Value;
				}
				return _serverValue;
			}
		}

		public T LocalValue
		{
			get
			{
				return Local.Value;
			}
			set
			{
				Local.Value = value;
			}
		}

		public bool IsLocked => _hasServerValue;

		internal SyncedEntry(ConfigEntry<T> local)
		{
			Local = local;
			Key = ((ConfigEntryBase)local).Definition.Section + "." + ((ConfigEntryBase)local).Definition.Key;
		}

		public string LocalAsString()
		{
			return TomlTypeConverter.ConvertToString((object)Local.Value, typeof(T));
		}

		public bool ApplyServerValue(string text, out string error)
		{
			object obj;
			try
			{
				obj = TomlTypeConverter.ConvertToValue(text, typeof(T));
			}
			catch (Exception ex)
			{
				error = "\"" + text + "\" is not a valid " + typeof(T).Name + " (" + ex.Message + ")";
				return false;
			}
			ConfigDescription description = ((ConfigEntryBase)Local).Description;
			AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null);
			if (val != null && !val.IsValid(obj))
			{
				obj = val.Clamp(obj);
			}
			_serverValue = (T)obj;
			_hasServerValue = true;
			error = string.Empty;
			return true;
		}

		public void ClearServerValue()
		{
			_hasServerValue = false;
			_serverValue = default(T);
		}

		public override string ToString()
		{
			T value = Value;
			return ((value != null) ? value.ToString() : null) ?? string.Empty;
		}
	}
}
namespace QuickTeleport
{
	public sealed class ModConfig
	{
		private readonly ConfigFile _config;

		public ConfigSync Sync { get; }

		public SyncedEntry<bool> Enabled { get; }

		public ConfigEntry<bool> IsDebug { get; }

		public SyncedEntry<TeleportMode> Mode { get; }

		public SyncedEntry<float> SpeedMultiplier { get; }

		public SyncedEntry<float> FadeDuration { get; }

		public SyncedEntry<bool> WaitForAreaLoad { get; }

		public SyncedEntry<bool> WaitForObjects { get; }

		public SyncedEntry<float> SettleTime { get; }

		public ModConfig(ConfigFile config)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Expected O, but got Unknown
			_config = config;
			Sync = new ConfigSync(config, "muindor.QuickTeleport", "Quick Teleport", "1.1.0", Plugin.Log);
			Enabled = Sync.Bind("General", "Enabled", defaultValue: true, "Enable this mod. Off = vanilla teleports.");
			IsDebug = config.Bind<bool>("General", "IsDebug", false, "Log the timeline of every teleport (screen black, area loaded, settled, finished).");
			Mode = Sync.Bind("Teleport", "Mode", TeleportMode.Auto, "Auto: no fixed waits, the teleport ends as soon as the screen is black and the destination is loaded. Multiplier: everything vanilla does (the fade, the 2 s move delay, the 8 s portal minimum, the 15 s floor timeout) divided by SpeedMultiplier.");
			SpeedMultiplier = Sync.Bind("Teleport", "SpeedMultiplier", 4f, new ConfigDescription("Multiplier mode only. 1 = vanilla timing, 4 = four times faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			FadeDuration = Sync.Bind("Teleport", "FadeDuration", 0.1f, new ConfigDescription("Seconds for the screen to fade to black before the teleport and back afterwards (vanilla: 1, default: 0.1). The player is never moved before the screen is black, so this is the shortest possible teleport. In Multiplier mode it is divided by SpeedMultiplier too.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 5f), Array.Empty<object>()));
			WaitForAreaLoad = Sync.Bind("Loading", "WaitForAreaLoad", defaultValue: true, "Wait until the destination is loaded before ending the teleport (vanilla). false: end it right after the fade, like the old QuickTeleport 'Skip Loading Area'; you may float or fall until the world appears.");
			WaitForObjects = Sync.Bind("Loading", "WaitForObjects", defaultValue: true, "Also wait for the objects of the destination (buildings, trees) to spawn, not only for the terrain (vanilla). false: like the old QuickTeleport 'Skip Loading Objects'; you may end up under a building floor. Ignored when WaitForAreaLoad is false.");
			SettleTime = Sync.Bind("Loading", "SettleTime", 0.5f, new ConfigDescription("Portals in Auto mode with WaitForObjects only. After the destination is loaded, wait this many seconds without new objects arriving from the server before ending the teleport. Never waits past the vanilla 8 s measured from the start of the teleport. 0 disables. Dungeon entrances never wait: their interior is loaded together with the entrance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
		}

		public void Reload()
		{
			_config.Reload();
		}

		public TeleportSettings ToSettings()
		{
			return new TeleportSettings
			{
				Mode = Mode.Value,
				SpeedMultiplier = SpeedMultiplier.Value,
				FadeDuration = FadeDuration.Value,
				WaitForAreaLoad = WaitForAreaLoad.Value,
				WaitForObjects = WaitForObjects.Value,
				SettleTime = SettleTime.Value
			};
		}

		public string Describe()
		{
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			string text = ((Mode.Value == TeleportMode.Multiplier) ? ("Multiplier x" + SpeedMultiplier.Value.ToString("0.##", invariantCulture)) : ("Auto (settle " + SettleTime.Value.ToString("0.##", invariantCulture) + " s)"));
			string text2 = ((!WaitForAreaLoad.Value) ? "no wait for area load" : ((!WaitForObjects.Value) ? "wait for terrain only" : "wait for terrain and objects"));
			return (Enabled.Value ? "enabled" : "disabled") + ", " + text + ", fade " + FadeDuration.Value.ToString("0.##", invariantCulture) + " s, " + text2 + ", " + Sync.Describe();
		}
	}
	[BepInPlugin("muindor.QuickTeleport", "Quick Teleport", "1.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		public static ManualLogSource Log { get; private set; }

		public static ModConfig Settings { get; private set; }

		public static bool Enabled
		{
			get
			{
				if (Settings != null)
				{
					return Settings.Enabled.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			ConfigChannel.Activate(Settings.Sync, Log);
			_harmony = new Harmony("muindor.QuickTeleport");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)("Quick Teleport 1.1.0 loaded: " + Settings.Describe()));
		}

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

		public static void Debug(string message)
		{
			if (Settings != null && Settings.IsDebug.Value)
			{
				Log.LogInfo((object)message);
			}
		}

		public static void PatchFailed(string patch, Exception exception)
		{
			Log.LogError((object)$"{patch} failed, falling back to vanilla behaviour: {exception}");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "muindor.QuickTeleport";

		public const string PLUGIN_NAME = "Quick Teleport";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
namespace QuickTeleport.Teleport
{
	public enum AreaCheck
	{
		Skip,
		TerrainOnly,
		Full
	}
	public enum TeleportMode
	{
		Auto,
		Multiplier
	}
	public sealed class TeleportPolicy
	{
		public const float VanillaMoveDelay = 2f;

		public const float VanillaDistantDelay = 8f;

		public const float VanillaFloorTimeout = 15f;

		private const float Epsilon = 0.001f;

		private int? _stableCount;

		private float _stableSince;

		public bool Distant { get; }

		public TeleportMode Mode { get; }

		public float SpeedMultiplier { get; }

		public float FadeDuration { get; }

		public float SettleTime { get; }

		public AreaCheck AreaCheck { get; }

		public float VanillaMinimum
		{
			get
			{
				if (!Distant)
				{
					return 2f;
				}
				return 8f;
			}
		}

		public float MoveFallback
		{
			get
			{
				if (Mode != TeleportMode.Multiplier)
				{
					return 2f;
				}
				return 2f / SpeedMultiplier;
			}
		}

		public float Elapsed { get; private set; }

		public float VirtualTimer { get; private set; }

		public bool ScreenBlack { get; private set; }

		public float? BlackAt { get; private set; }

		public float? MovedAt { get; private set; }

		public float? AreaReadyAt { get; private set; }

		public int? ObjectCount { get; private set; }

		public float? SettledAt { get; private set; }

		public float? FinishedAt { get; private set; }

		public bool Finished => FinishedAt.HasValue;

		public TeleportPolicy(TeleportSettings settings, bool distant)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			Distant = distant;
			Mode = settings.Mode;
			SpeedMultiplier = Math.Max(1f, settings.SpeedMultiplier);
			SettleTime = Math.Max(0f, settings.SettleTime);
			float num = settings.FadeDuration;
			if (Mode == TeleportMode.Multiplier)
			{
				num /= SpeedMultiplier;
			}
			FadeDuration = Math.Max(0.05f, num);
			AreaCheck = (settings.WaitForAreaLoad ? ((!settings.WaitForObjects) ? AreaCheck.TerrainOnly : AreaCheck.Full) : AreaCheck.Skip);
		}

		public float Advance(float dt, bool hudBlack = true)
		{
			Elapsed += Math.Max(0f, dt);
			if (!ScreenBlack)
			{
				ScreenBlack = Elapsed >= FadeDuration && (hudBlack || Elapsed >= MoveFallback);
				if (!ScreenBlack)
				{
					VirtualTimer = 0f;
					return 0f;
				}
				BlackAt = Elapsed;
			}
			float num;
			if (Mode == TeleportMode.Multiplier)
			{
				num = Elapsed * SpeedMultiplier;
			}
			else
			{
				num = Math.Min(Elapsed + VanillaMinimum, 15f);
				if (Elapsed > 15f)
				{
					num = Elapsed;
				}
			}
			if (AreaCheck == AreaCheck.Skip)
			{
				num = Math.Max(num, 15.001f);
			}
			if (num > 2f && !MovedAt.HasValue)
			{
				MovedAt = Elapsed;
			}
			VirtualTimer = num;
			return num;
		}

		public bool ApplySettle(bool areaReady, int objectCount)
		{
			if (!areaReady)
			{
				_stableCount = null;
				return false;
			}
			if (!AreaReadyAt.HasValue)
			{
				AreaReadyAt = Elapsed;
			}
			ObjectCount = objectCount;
			bool flag;
			if (Mode != TeleportMode.Auto || AreaCheck != AreaCheck.Full || !Distant)
			{
				flag = true;
			}
			else
			{
				if (_stableCount != objectCount)
				{
					_stableCount = objectCount;
					_stableSince = Elapsed;
				}
				flag = SettleTime <= 0f || Elapsed - _stableSince >= SettleTime || Elapsed >= VanillaMinimum;
			}
			if (flag && !SettledAt.HasValue)
			{
				SettledAt = Elapsed;
			}
			return flag;
		}

		public void MarkFinished()
		{
			if (!FinishedAt.HasValue)
			{
				FinishedAt = Elapsed;
			}
		}

		public string Describe()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(Distant ? "Portal" : "Dungeon").Append(" teleport, ").Append(Mode);
			if (Mode == TeleportMode.Multiplier)
			{
				stringBuilder.Append(" x").Append(SpeedMultiplier.ToString("0.##", CultureInfo.InvariantCulture));
			}
			stringBuilder.Append(": screen black at ").Append(Seconds(BlackAt));
			stringBuilder.Append(", moved at ").Append(Seconds(MovedAt));
			switch (AreaCheck)
			{
			case AreaCheck.Skip:
				stringBuilder.Append(", area load skipped");
				break;
			case AreaCheck.TerrainOnly:
				stringBuilder.Append(", terrain loaded at ").Append(Seconds(AreaReadyAt));
				break;
			default:
				stringBuilder.Append(", area loaded at ").Append(Seconds(AreaReadyAt));
				if (ObjectCount.HasValue)
				{
					stringBuilder.Append(" (").Append(ObjectCount.Value).Append(" objects)");
				}
				if (Mode == TeleportMode.Auto && Distant)
				{
					stringBuilder.Append(", settled at ").Append(Seconds(SettledAt));
				}
				break;
			}
			stringBuilder.Append(", finished at ").Append(Seconds(FinishedAt));
			stringBuilder.Append("; vanilla waits at least ").Append(VanillaMinimum.ToString("0", CultureInfo.InvariantCulture)).Append(" s");
			return stringBuilder.ToString();
		}

		private static string Seconds(float? time)
		{
			if (!time.HasValue)
			{
				return "never";
			}
			return time.Value.ToString("0.00", CultureInfo.InvariantCulture) + " s";
		}
	}
	public sealed class TeleportSettings
	{
		public const float MinFadeDuration = 0.05f;

		public const float DefaultFadeDuration = 0.1f;

		public TeleportMode Mode { get; set; }

		public float SpeedMultiplier { get; set; } = 4f;

		public float FadeDuration { get; set; } = 0.1f;

		public bool WaitForAreaLoad { get; set; } = true;

		public bool WaitForObjects { get; set; } = true;

		public float SettleTime { get; set; } = 0.5f;
	}
}
namespace QuickTeleport.Patches
{
	[HarmonyPatch(typeof(ZNetScene), "IsAreaReady")]
	internal static class ZNetScene_IsAreaReady_Patch
	{
		private static bool Prefix(Vector3 point, ref bool __result)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			TeleportPolicy updating = ActiveTeleport.Updating;
			if (updating == null)
			{
				return true;
			}
			try
			{
				switch (updating.AreaCheck)
				{
				case AreaCheck.Skip:
					__result = true;
					return false;
				case AreaCheck.TerrainOnly:
					__result = ZoneSystem.instance.IsZoneLoaded(point);
					return false;
				default:
					return true;
				}
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("ZNetScene_IsAreaReady_Patch", exception);
				return true;
			}
		}

		private static void Postfix(ZNetScene __instance, ref bool __result)
		{
			TeleportPolicy updating = ActiveTeleport.Updating;
			if (updating == null)
			{
				return;
			}
			try
			{
				int objectCount = ((updating.AreaCheck == AreaCheck.Full) ? __instance.m_tempCurrentObjects.Count : 0);
				__result = updating.ApplySettle(__result, objectCount);
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("ZNetScene_IsAreaReady_Patch", exception);
			}
		}
	}
	[HarmonyPatch(typeof(Hud), "GetFadeDuration")]
	internal static class Hud_GetFadeDuration_Patch
	{
		private static void Postfix(Hud __instance, Player player, ref float __result)
		{
			if (!Plugin.Enabled || (Object)(object)player == (Object)null)
			{
				return;
			}
			try
			{
				if (((Character)player).IsDead() || player.IsSleeping())
				{
					ActiveTeleport.FadingFromTeleport = false;
				}
				else if (((Character)player).IsTeleporting())
				{
					TeleportPolicy teleportPolicy = ActiveTeleport.For(player);
					if (teleportPolicy != null)
					{
						__result = teleportPolicy.FadeDuration;
					}
				}
				else if (ActiveTeleport.FadingFromTeleport)
				{
					if ((Object)(object)ActiveTeleport.FadingPlayer != (Object)(object)player)
					{
						ActiveTeleport.FadingFromTeleport = false;
					}
					else if (__instance.m_loadingScreen.alpha > 0f)
					{
						__result = ActiveTeleport.LastFade;
					}
					else
					{
						ActiveTeleport.FadingFromTeleport = false;
					}
				}
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Hud_GetFadeDuration_Patch", exception);
			}
		}
	}
	internal static class ActiveTeleport
	{
		public static Player? Player { get; private set; }

		public static TeleportPolicy? Policy { get; private set; }

		public static TeleportPolicy? Updating { get; set; }

		public static float LastFade { get; private set; } = 1f;

		public static bool FadingFromTeleport { get; set; }

		public static Player? FadingPlayer { get; private set; }

		public static bool PositionAnnounced { get; set; }

		public static TeleportPolicy Start(Player player, bool distant, float elapsed = 0f)
		{
			TeleportPolicy teleportPolicy = new TeleportPolicy(Plugin.Settings.ToSettings(), distant);
			if (elapsed > 0f)
			{
				teleportPolicy.Advance(elapsed);
			}
			Player = player;
			Policy = teleportPolicy;
			LastFade = teleportPolicy.FadeDuration;
			FadingFromTeleport = true;
			FadingPlayer = player;
			PositionAnnounced = false;
			return teleportPolicy;
		}

		public static bool HudBlack()
		{
			Hud instance = Hud.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_loadingScreen == (Object)null))
			{
				return instance.m_loadingScreen.alpha >= 1f;
			}
			return true;
		}

		public static TeleportPolicy? For(Player player)
		{
			if (!((Object)(object)Player == (Object)(object)player))
			{
				return null;
			}
			return Policy;
		}

		public static void Clear()
		{
			Player = null;
			Policy = null;
			Updating = null;
		}
	}
	[HarmonyPatch(typeof(Player), "TeleportTo")]
	internal static class Player_TeleportTo_Patch
	{
		private static void Postfix(Player __instance, Vector3 pos, bool distantTeleport, bool __result)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (!__result)
			{
				return;
			}
			ActiveTeleport.Clear();
			if (!Plugin.Enabled)
			{
				return;
			}
			try
			{
				TeleportPolicy teleportPolicy = ActiveTeleport.Start(__instance, distantTeleport);
				Plugin.Debug(string.Format("{0} teleport to {1} started: {2}, fade {3:0.00} s, area check {4}", distantTeleport ? "Portal" : "Dungeon", pos, teleportPolicy.Mode, teleportPolicy.FadeDuration, teleportPolicy.AreaCheck));
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Player_TeleportTo_Patch", exception);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "UpdateTeleport")]
	internal static class Player_UpdateTeleport_Patch
	{
		private static void Prefix(Player __instance, ref float dt)
		{
			if (!Plugin.Enabled || !__instance.m_teleporting)
			{
				return;
			}
			try
			{
				TeleportPolicy teleportPolicy = ActiveTeleport.For(__instance) ?? ActiveTeleport.Start(__instance, __instance.m_distantTeleport, __instance.m_teleportTimer);
				__instance.m_teleportTimer = teleportPolicy.Advance(dt, ActiveTeleport.HudBlack());
				dt = 0f;
				ActiveTeleport.Updating = teleportPolicy;
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Player_UpdateTeleport_Patch", exception);
			}
		}

		private static void Postfix(Player __instance)
		{
			TeleportPolicy updating = ActiveTeleport.Updating;
			ActiveTeleport.Updating = null;
			if (updating != null)
			{
				if (updating.MovedAt.HasValue && !ActiveTeleport.PositionAnnounced)
				{
					AnnouncePosition(__instance);
				}
				if (!__instance.m_teleporting)
				{
					updating.MarkFinished();
					Plugin.Debug("Teleport finished: " + updating.Describe());
					ActiveTeleport.Clear();
				}
			}
		}

		private static void AnnouncePosition(Player player)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			ActiveTeleport.PositionAnnounced = true;
			try
			{
				ZNet instance = ZNet.instance;
				ZDO val = (((Object)(object)((Character)player).m_nview != (Object)null) ? ((Character)player).m_nview.GetZDO() : null);
				if (!((Object)(object)instance == (Object)null) && val != null)
				{
					Vector3 position = ((Component)player).transform.position;
					val.SetPosition(position);
					instance.SetReferencePosition(position);
					instance.m_periodicSendTimer = 2f;
					Plugin.Debug($"Reported the new position {position} to the server");
				}
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Player_UpdateTeleport_Patch", exception);
			}
		}

		private static Exception? Finalizer(Exception? __exception)
		{
			ActiveTeleport.Updating = null;
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class Terminal_InitTerminal_Patch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Execute;
		}

		private const string Command = "quickteleport";

		private static void Postfix()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			object obj = <>O.<0>__Execute;
			if (obj == null)
			{
				ConsoleEvent val = Execute;
				<>O.<0>__Execute = val;
				obj = (object)val;
			}
			new ConsoleCommand("quickteleport", "quickteleport [reload|status] - reload the Quick Teleport config or show the active settings", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void Execute(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status");
			if (!(text == "reload"))
			{
				if (text == "status")
				{
					context.AddString("Quick Teleport 1.1.0: " + Plugin.Settings.Describe());
				}
				else
				{
					context.AddString("Usage: quickteleport [reload|status]");
				}
			}
			else
			{
				Plugin.Settings.Reload();
				context.AddString("Quick Teleport: config reloaded, " + Plugin.Settings.Describe());
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}