Decompiled source of UnstablePortalsMetalTransport v1.2.2

UnstablePortalsMetalTransport.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("KernelPanik")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyInformationalVersion("1.2.2")]
[assembly: AssemblyProduct("UnstablePortalsMetalTransport")]
[assembly: AssemblyTitle("UnstablePortalsMetalTransport")]
[assembly: AssemblyVersion("1.2.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace UnstablePortalsMetalTransport
{
	[BepInPlugin("com.kernelpanik.unstableportalsmetaltransport", "Unstable Portals: Metal Transport", "1.2.2")]
	public sealed class UnstablePortalsMetalTransportPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.kernelpanik.unstableportalsmetaltransport";

		public const string PluginName = "Unstable Portals: Metal Transport";

		public const string PluginVersion = "1.2.2";

		private Harmony _harmony;

		internal static ConfigEntry<int> SurtlingCoreCost { get; private set; }

		internal static ConfigEntry<int> GreydwarfEyeCost { get; private set; }

		internal static ConfigEntry<float> PortalDamagePercent { get; private set; }

		internal static ConfigEntry<bool> DamageSourcePortal { get; private set; }

		internal static ConfigEntry<bool> DamageDestinationPortal { get; private set; }

		internal static ConfigEntry<bool> InverseRunePulse { get; private set; }

		internal static ConfigEntry<float> RuneBrightnessMultiplier { get; private set; }

		internal static ConfigEntry<bool> EnableUnstableSound { get; private set; }

		internal static ConfigEntry<float> MinimumPitchMultiplier { get; private set; }

		internal static ConfigEntry<float> MaximumPitchMultiplier { get; private set; }

		private void Awake()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: 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_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Expected O, but got Unknown
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Expected O, but got Unknown
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Expected O, but got Unknown
			SurtlingCoreCost = ((BaseUnityPlugin)this).Config.Bind<int>("Toll", "SurtlingCoreCost", 1, new ConfigDescription("Surtling Cores consumed after a successful restricted-item portal trip.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			GreydwarfEyeCost = ((BaseUnityPlugin)this).Config.Bind<int>("Toll", "GreydwarfEyeCost", 5, new ConfigDescription("Greydwarf Eyes consumed after a successful restricted-item portal trip.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
			PortalDamagePercent = ((BaseUnityPlugin)this).Config.Bind<float>("Portal Damage", "DamagePercent", 10f, new ConfigDescription("Percentage of each affected portal's maximum health removed per paid trip.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			DamageSourcePortal = ((BaseUnityPlugin)this).Config.Bind<bool>("Portal Damage", "DamageSourcePortal", true, "Damage the portal the player enters after a successful paid trip.");
			DamageDestinationPortal = ((BaseUnityPlugin)this).Config.Bind<bool>("Portal Damage", "DamageDestinationPortal", true, "Damage the linked destination portal after a successful paid trip.");
			InverseRunePulse = ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "InverseRunePulse", true, "Pulse the portal frame emission/runes in the opposite color phase from the particles and light.");
			RuneBrightnessMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Visuals", "RuneBrightnessMultiplier", 3f, new ConfigDescription("HDR brightness multiplier for the pulsing frame emission/runes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			EnableUnstableSound = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "EnableUnstableSound", true, "Pitch-wobble the portal's existing sound while the restricted-item toll effect is active.");
			MinimumPitchMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "MinimumPitchMultiplier", 0.82f, new ConfigDescription("Lowest pitch multiplier used by the instability wobble. Values below 1 sound deeper.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>()));
			MaximumPitchMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "MaximumPitchMultiplier", 0.96f, new ConfigDescription("Highest pitch multiplier used by the instability wobble. Values above 1 sound higher.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>()));
			_harmony = new Harmony("com.kernelpanik.unstableportalsmetaltransport");
			try
			{
				PortalPatches.Install(_harmony, ((BaseUnityPlugin)this).Logger);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Unstable Portals: Metal Transport 1.2.2 loaded.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not install portal patches: " + ex));
			}
		}

		private void Update()
		{
			PortalPatches.UpdatePortalVisuals();
			PortalPatches.UpdatePendingPortalDamage();
		}

		private void OnDestroy()
		{
			PortalPatches.RestorePortalVisuals();
			PortalPatches.ClearPendingPortalDamage();
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class PortalPatches
	{
		private sealed class PendingTrip
		{
			public DateTime ExpiresUtc;

			public object Inventory;

			public object SourcePortal;

			public object DestinationPortalId;

			public bool EligibilityOverrideUsed;
		}

		private sealed class PendingPortalDamage
		{
			public DateTime ExpiresUtc;

			public object DestinationPortalId;

			public float DamagePercent;
		}

		private sealed class InventorySnapshot
		{
			public object Inventory;

			public bool HasEligibleRestrictedItem;

			public bool HasAbsoluteRestriction;

			public int CoreCount;

			public int EyeCount;
		}

		private sealed class ParticleVisualState
		{
			public object Particle;

			public PropertyInfo MainProperty;

			public PropertyInfo StartColorProperty;

			public ConstructorInfo GradientConstructor;

			public object OriginalStartColor;
		}

		private sealed class AudioVisualState
		{
			public object AudioSource;

			public PropertyInfo PitchProperty;

			public float OriginalPitch;
		}

		private sealed class PortalVisualState
		{
			public FieldInfo TargetColorField;

			public object OriginalTargetColor;

			public ConstructorInfo ColorConstructor;

			public object Light;

			public PropertyInfo LightColorProperty;

			public object OriginalLightColor;

			public readonly List<ParticleVisualState> Particles = new List<ParticleVisualState>();

			public readonly List<AudioVisualState> AudioSources = new List<AudioVisualState>();

			public bool TollEffectActive;
		}

		private const string CorePrefabName = "SurtlingCore";

		private const string EyePrefabName = "GreydwarfEye";

		private static readonly TimeSpan PendingTripLifetime = TimeSpan.FromSeconds(3.0);

		private static readonly TimeSpan PendingDamageLifetime = TimeSpan.FromSeconds(20.0);

		private static readonly ConditionalWeakTable<object, PendingTrip> PendingTrips = new ConditionalWeakTable<object, PendingTrip>();

		private static readonly ConditionalWeakTable<object, PortalVisualState> PortalVisualStates = new ConditionalWeakTable<object, PortalVisualState>();

		private static readonly List<WeakReference> VisualPortals = new List<WeakReference>();

		private static readonly List<PendingPortalDamage> PendingDestinationDamage = new List<PendingPortalDamage>();

		private static ManualLogSource _log;

		private static Type _playerType;

		private static Type _wearNTearType;

		private static Type _hitDataType;

		private static Type _zNetSceneType;

		private static Type _audioSourceType;

		private static DateTime _nextDamagePollUtc;

		[ThreadStatic]
		private static object _portalBeingUpdated;

		[ThreadStatic]
		private static bool _portalUpdateUsesToll;

		private static int ConfiguredCoreCost => Math.Max(0, (UnstablePortalsMetalTransportPlugin.SurtlingCoreCost == null) ? 1 : UnstablePortalsMetalTransportPlugin.SurtlingCoreCost.Value);

		private static int ConfiguredEyeCost => Math.Max(0, (UnstablePortalsMetalTransportPlugin.GreydwarfEyeCost == null) ? 5 : UnstablePortalsMetalTransportPlugin.GreydwarfEyeCost.Value);

		private static float ConfiguredDamagePercent
		{
			get
			{
				float val = ((UnstablePortalsMetalTransportPlugin.PortalDamagePercent == null) ? 10f : UnstablePortalsMetalTransportPlugin.PortalDamagePercent.Value);
				return Math.Max(0f, Math.Min(100f, val));
			}
		}

		private static bool ConfiguredDamageSource
		{
			get
			{
				if (UnstablePortalsMetalTransportPlugin.DamageSourcePortal != null)
				{
					return UnstablePortalsMetalTransportPlugin.DamageSourcePortal.Value;
				}
				return true;
			}
		}

		private static bool ConfiguredDamageDestination
		{
			get
			{
				if (UnstablePortalsMetalTransportPlugin.DamageDestinationPortal != null)
				{
					return UnstablePortalsMetalTransportPlugin.DamageDestinationPortal.Value;
				}
				return true;
			}
		}

		private static bool ConfiguredInverseRunePulse
		{
			get
			{
				if (UnstablePortalsMetalTransportPlugin.InverseRunePulse != null)
				{
					return UnstablePortalsMetalTransportPlugin.InverseRunePulse.Value;
				}
				return true;
			}
		}

		private static float ConfiguredRuneBrightnessMultiplier
		{
			get
			{
				float val = ((UnstablePortalsMetalTransportPlugin.RuneBrightnessMultiplier == null) ? 3f : UnstablePortalsMetalTransportPlugin.RuneBrightnessMultiplier.Value);
				return Math.Max(0.5f, Math.Min(10f, val));
			}
		}

		private static bool ConfiguredUnstableSound
		{
			get
			{
				if (UnstablePortalsMetalTransportPlugin.EnableUnstableSound != null)
				{
					return UnstablePortalsMetalTransportPlugin.EnableUnstableSound.Value;
				}
				return true;
			}
		}

		private static float ConfiguredMinimumPitchMultiplier
		{
			get
			{
				float val = ((UnstablePortalsMetalTransportPlugin.MinimumPitchMultiplier == null) ? 0.82f : UnstablePortalsMetalTransportPlugin.MinimumPitchMultiplier.Value);
				return Math.Max(0.5f, Math.Min(1.5f, val));
			}
		}

		private static float ConfiguredMaximumPitchMultiplier
		{
			get
			{
				float val = ((UnstablePortalsMetalTransportPlugin.MaximumPitchMultiplier == null) ? 0.96f : UnstablePortalsMetalTransportPlugin.MaximumPitchMultiplier.Value);
				return Math.Max(0.5f, Math.Min(1.5f, val));
			}
		}

		internal static void Install(Harmony harmony, ManualLogSource log)
		{
			_log = log;
			Type type = RequireType("TeleportWorld");
			Type type2 = RequireType("Humanoid");
			_playerType = RequireType("Player");
			_wearNTearType = RequireType("WearNTear");
			_hitDataType = RequireType("HitData");
			_zNetSceneType = RequireType("ZNetScene");
			_audioSourceType = AccessTools.TypeByName("UnityEngine.AudioSource");
			MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo methodInfo3) => methodInfo3.Name == "Teleport" && methodInfo3.GetParameters().Any((ParameterInfo parameter) => parameter.ParameterType.IsAssignableFrom(_playerType) || _playerType.IsAssignableFrom(parameter.ParameterType)));
			if (methodInfo == null)
			{
				throw new MissingMethodException("TeleportWorld.Teleport(Player)");
			}
			MethodInfo method = type2.GetMethod("IsTeleportable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null);
			if (method == null || method.ReturnType != typeof(bool))
			{
				throw new MissingMethodException("Humanoid.IsTeleportable(bool)");
			}
			MethodInfo methodInfo2 = _playerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo methodInfo3) => methodInfo3.Name == "TeleportTo" && methodInfo3.DeclaringType == _playerType && methodInfo3.ReturnType == typeof(bool));
			if (methodInfo2 == null)
			{
				throw new MissingMethodException("Player.TeleportTo(...)");
			}
			MethodInfo method2 = type.GetMethod("UpdatePortal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method2 == null)
			{
				throw new MissingMethodException("TeleportWorld.UpdatePortal()");
			}
			HarmonyMethod val = PatchMethod("PortalTeleportPrefix");
			HarmonyMethod val2 = PatchMethod("PortalTeleportPostfix");
			HarmonyMethod val3 = PatchMethod("HumanoidIsTeleportablePostfix");
			HarmonyMethod val4 = PatchMethod("PlayerTeleportToPostfix");
			HarmonyMethod val5 = PatchMethod("PortalUpdatePrefix");
			HarmonyMethod val6 = PatchMethod("PortalUpdatePostfix");
			harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)method, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)method2, val5, val6, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_log.LogInfo((object)("Patched " + methodInfo.DeclaringType.Name + "." + methodInfo.Name + ", " + method.DeclaringType.Name + "." + method.Name + ", and " + methodInfo2.DeclaringType.Name + "." + methodInfo2.Name + "; toll eligibility is tracked through " + method2.DeclaringType.Name + "." + method2.Name + " and pulsing visuals use the plugin frame loop."));
		}

		private static HarmonyMethod PatchMethod(string name)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return new HarmonyMethod(typeof(PortalPatches).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic));
		}

		private static Type RequireType(string name)
		{
			Type type = AccessTools.TypeByName(name);
			if (type == null)
			{
				throw new TypeLoadException("Could not find Valheim type " + name + ".");
			}
			return type;
		}

		private static void PortalTeleportPrefix(object __instance, object[] __args)
		{
			object obj = FindPlayer(__args);
			if (obj == null || !IsLocalPlayer(obj))
			{
				return;
			}
			RemovePendingTrip(obj);
			if (ReadBoolField(__instance, "m_allowAllItems"))
			{
				return;
			}
			InventorySnapshot inventorySnapshot = ReadInventory(obj);
			if (inventorySnapshot != null && inventorySnapshot.HasEligibleRestrictedItem && !inventorySnapshot.HasAbsoluteRestriction)
			{
				if (!HasRequiredToll(inventorySnapshot))
				{
					ShowCenterMessage(obj, "You need " + DescribeToll() + " to use a portal while carrying metal.");
				}
				else
				{
					SetPendingTrip(obj, inventorySnapshot.Inventory, __instance, GetConnectedPortalId(__instance));
				}
			}
		}

		private static void PortalTeleportPostfix(object[] __args)
		{
			object obj = FindPlayer(__args);
			if (obj != null && HasPendingTrip(obj))
			{
				RemovePendingTrip(obj);
			}
		}

		private static void PortalUpdatePrefix(object __instance)
		{
			_portalBeingUpdated = __instance;
			_portalUpdateUsesToll = false;
		}

		private static void PortalUpdatePostfix(object __instance)
		{
			try
			{
				bool active = _portalBeingUpdated == __instance && _portalUpdateUsesToll;
				SetPortalTollEffect(__instance, active);
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("Could not update the unstable portal effect: " + ex.Message));
			}
			finally
			{
				_portalBeingUpdated = null;
				_portalUpdateUsesToll = false;
			}
		}

		internal static void UpdatePortalVisuals()
		{
			if (VisualPortals.Count == 0)
			{
				return;
			}
			float pulseBlend = GetPulseBlend(DateTime.UtcNow);
			for (int num = VisualPortals.Count - 1; num >= 0; num--)
			{
				object target = VisualPortals[num].Target;
				PortalVisualState value;
				if (target == null)
				{
					VisualPortals.RemoveAt(num);
				}
				else if (PortalVisualStates.TryGetValue(target, out value) && value.TollEffectActive)
				{
					try
					{
						ApplyPortalPulseFrame(target, value, pulseBlend);
					}
					catch (Exception ex)
					{
						_log.LogDebug((object)("Stopped animating a portal visual: " + ex.Message));
						VisualPortals.RemoveAt(num);
					}
				}
			}
		}

		private static void HumanoidIsTeleportablePostfix(object __instance, ref bool __result)
		{
			if (__result || __instance == null || !_playerType.IsInstanceOfType(__instance) || !IsLocalPlayer(__instance))
			{
				return;
			}
			InventorySnapshot inventorySnapshot = ReadInventory(__instance);
			if (inventorySnapshot != null && inventorySnapshot.HasEligibleRestrictedItem && !inventorySnapshot.HasAbsoluteRestriction && HasRequiredToll(inventorySnapshot))
			{
				__result = true;
				if (_portalBeingUpdated != null)
				{
					_portalUpdateUsesToll = true;
				}
				PendingTrip pendingTrip = GetPendingTrip(__instance);
				if (pendingTrip != null)
				{
					pendingTrip.EligibilityOverrideUsed = true;
				}
			}
		}

		private static void PlayerTeleportToPostfix(object __instance, ref bool __result)
		{
			if (!__result || __instance == null)
			{
				return;
			}
			PendingTrip pendingTrip = GetPendingTrip(__instance);
			if (pendingTrip == null || !pendingTrip.EligibilityOverrideUsed)
			{
				return;
			}
			if (!ConsumeToll(pendingTrip.Inventory))
			{
				_log.LogWarning((object)"Portal travel succeeded, but the configured toll could not be removed.");
				RemovePendingTrip(__instance);
				return;
			}
			float configuredDamagePercent = ConfiguredDamagePercent;
			if (configuredDamagePercent > 0f && ConfiguredDamageSource)
			{
				ApplyPortalDamage(pendingTrip.SourcePortal, configuredDamagePercent);
			}
			if (configuredDamagePercent > 0f && ConfiguredDamageDestination && pendingTrip.DestinationPortalId != null)
			{
				QueueDestinationDamage(pendingTrip.DestinationPortalId, configuredDamagePercent);
			}
			RemovePendingTrip(__instance);
			string text = DescribeToll();
			if (ConfiguredCoreCost > 0 || ConfiguredEyeCost > 0)
			{
				ShowCenterMessage(__instance, "The portal consumes " + text + ".");
			}
			_log.LogInfo((object)("Charged " + text + " for restricted-item portal travel."));
		}

		private static object FindPlayer(object[] arguments)
		{
			if (arguments == null || _playerType == null)
			{
				return null;
			}
			return arguments.FirstOrDefault((object argument) => argument != null && _playerType.IsInstanceOfType(argument));
		}

		private static bool IsLocalPlayer(object player)
		{
			FieldInfo field = _playerType.GetField("m_localPlayer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = ((field == null) ? null : field.GetValue(null));
			if (obj != null)
			{
				return obj == player;
			}
			return true;
		}

		private static void SetPortalTollEffect(object portal, bool active)
		{
			if (portal == null)
			{
				return;
			}
			if (!PortalVisualStates.TryGetValue(portal, out var value))
			{
				value = CapturePortalVisualState(portal);
				PortalVisualStates.Add(portal, value);
				VisualPortals.Add(new WeakReference(portal));
			}
			if (value.TollEffectActive == active || (active && value.ColorConstructor == null))
			{
				return;
			}
			value.TollEffectActive = active;
			if (active)
			{
				ApplyPortalPulseFrame(portal, value, GetPulseBlend(DateTime.UtcNow));
				return;
			}
			if (value.TargetColorField != null && value.OriginalTargetColor != null)
			{
				value.TargetColorField.SetValue(portal, value.OriginalTargetColor);
			}
			foreach (ParticleVisualState particle in value.Particles)
			{
				object value2 = particle.MainProperty.GetValue(particle.Particle, null);
				particle.StartColorProperty.SetValue(value2, particle.OriginalStartColor, null);
			}
			if (value.Light != null && value.LightColorProperty != null)
			{
				value.LightColorProperty.SetValue(value.Light, value.OriginalLightColor, null);
			}
			RestorePortalAudio(value);
		}

		private static void ApplyPortalPulseFrame(object portal, PortalVisualState state, float purpleBlend)
		{
			if (state.ColorConstructor == null)
			{
				return;
			}
			object obj = CreateBlendedPulseColor(state, purpleBlend);
			float purpleBlend2 = (ConfiguredInverseRunePulse ? (1f - purpleBlend) : purpleBlend);
			object obj2 = CreateBlendedPulseColor(state, purpleBlend2, ConfiguredRuneBrightnessMultiplier);
			if (obj == null || obj2 == null)
			{
				return;
			}
			if (state.TargetColorField != null)
			{
				state.TargetColorField.SetValue(portal, obj2);
			}
			foreach (ParticleVisualState particle in state.Particles)
			{
				object value = particle.MainProperty.GetValue(particle.Particle, null);
				object value2 = particle.GradientConstructor.Invoke(new object[1] { obj });
				particle.StartColorProperty.SetValue(value, value2, null);
			}
			if (state.Light != null && state.LightColorProperty != null)
			{
				state.LightColorProperty.SetValue(state.Light, obj, null);
			}
			ApplyPortalAudioPitch(state, purpleBlend);
		}

		private static void ApplyPortalAudioPitch(PortalVisualState state, float purpleBlend)
		{
			float num = Math.Min(ConfiguredMinimumPitchMultiplier, ConfiguredMaximumPitchMultiplier);
			float num2 = Math.Max(ConfiguredMinimumPitchMultiplier, ConfiguredMaximumPitchMultiplier);
			float num3 = (ConfiguredUnstableSound ? (num + (num2 - num) * purpleBlend) : 1f);
			foreach (AudioVisualState audioSource in state.AudioSources)
			{
				try
				{
					audioSource.PitchProperty.SetValue(audioSource.AudioSource, audioSource.OriginalPitch * num3, null);
				}
				catch
				{
				}
			}
		}

		private static void RestorePortalAudio(PortalVisualState state)
		{
			foreach (AudioVisualState audioSource in state.AudioSources)
			{
				try
				{
					audioSource.PitchProperty.SetValue(audioSource.AudioSource, audioSource.OriginalPitch, null);
				}
				catch
				{
				}
			}
		}

		private static float GetPulseBlend(DateTime utcNow)
		{
			double num = (double)utcNow.Ticks / 10000000.0 % 2.6 / 2.6;
			return (float)(0.5 - 0.5 * Math.Cos(num * Math.PI * 2.0));
		}

		private static object CreateBlendedPulseColor(PortalVisualState state, float purpleBlend, float brightnessMultiplier = 1f)
		{
			float num = 1f - purpleBlend;
			return state.ColorConstructor.Invoke(new object[4]
			{
				(1f * num + 0.62f * purpleBlend) * brightnessMultiplier,
				(0.12f * num + 0.06f * purpleBlend) * brightnessMultiplier,
				(0.015f * num + 1.05f * purpleBlend) * brightnessMultiplier,
				1f
			});
		}

		private static PortalVisualState CapturePortalVisualState(object portal)
		{
			PortalVisualState portalVisualState = new PortalVisualState();
			portalVisualState.TargetColorField = FindField(portal.GetType(), "m_colorTargetfound");
			if (portalVisualState.TargetColorField == null)
			{
				return portalVisualState;
			}
			portalVisualState.OriginalTargetColor = portalVisualState.TargetColorField.GetValue(portal);
			portalVisualState.ColorConstructor = portalVisualState.TargetColorField.FieldType.GetConstructor(new Type[4]
			{
				typeof(float),
				typeof(float),
				typeof(float),
				typeof(float)
			});
			if (portalVisualState.ColorConstructor == null)
			{
				return portalVisualState;
			}
			object obj = ReadField(portal, "m_target_found");
			IEnumerable enumerable = ((obj == null) ? null : (ReadField(obj, "m_particles") as IEnumerable));
			if (enumerable != null)
			{
				foreach (object item in enumerable)
				{
					if (item == null)
					{
						continue;
					}
					PropertyInfo property = item.GetType().GetProperty("main", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					object obj2 = ((property == null) ? null : property.GetValue(item, null));
					PropertyInfo propertyInfo = obj2?.GetType().GetProperty("startColor", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (!(propertyInfo == null) && propertyInfo.CanRead && propertyInfo.CanWrite)
					{
						ConstructorInfo constructor = propertyInfo.PropertyType.GetConstructor(new Type[1] { portalVisualState.TargetColorField.FieldType });
						if (!(constructor == null))
						{
							portalVisualState.Particles.Add(new ParticleVisualState
							{
								Particle = item,
								MainProperty = property,
								StartColorProperty = propertyInfo,
								GradientConstructor = constructor,
								OriginalStartColor = propertyInfo.GetValue(obj2, null)
							});
						}
					}
				}
			}
			portalVisualState.Light = ((obj == null) ? null : ReadField(obj, "m_light"));
			if (portalVisualState.Light != null)
			{
				portalVisualState.LightColorProperty = portalVisualState.Light.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (portalVisualState.LightColorProperty != null && portalVisualState.LightColorProperty.CanRead && portalVisualState.LightColorProperty.CanWrite)
				{
					portalVisualState.OriginalLightColor = portalVisualState.LightColorProperty.GetValue(portalVisualState.Light, null);
				}
				else
				{
					portalVisualState.LightColorProperty = null;
				}
			}
			CapturePortalAudioSources(portal, portalVisualState);
			return portalVisualState;
		}

		private static void CapturePortalAudioSources(object portal, PortalVisualState state)
		{
			if (_audioSourceType == null)
			{
				return;
			}
			MethodInfo methodInfo = portal.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo method)
			{
				ParameterInfo[] parameters2 = method.GetParameters();
				return method.Name == "GetComponentsInChildren" && !method.IsGenericMethod && parameters2.Length == 2 && parameters2[0].ParameterType == typeof(Type) && parameters2[1].ParameterType == typeof(bool);
			});
			object[] parameters = new object[2] { _audioSourceType, true };
			if (methodInfo == null)
			{
				methodInfo = portal.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo method)
				{
					ParameterInfo[] parameters2 = method.GetParameters();
					return method.Name == "GetComponentsInChildren" && !method.IsGenericMethod && parameters2.Length == 1 && parameters2[0].ParameterType == typeof(Type);
				});
				parameters = new object[1] { _audioSourceType };
			}
			if (methodInfo == null || !(methodInfo.Invoke(portal, parameters) is IEnumerable enumerable))
			{
				return;
			}
			foreach (object item in enumerable)
			{
				if (item != null)
				{
					PropertyInfo property = item.GetType().GetProperty("pitch", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (!(property == null) && property.CanRead && property.CanWrite)
					{
						state.AudioSources.Add(new AudioVisualState
						{
							AudioSource = item,
							PitchProperty = property,
							OriginalPitch = Convert.ToSingle(property.GetValue(item, null))
						});
					}
				}
			}
		}

		internal static void RestorePortalVisuals()
		{
			foreach (WeakReference visualPortal in VisualPortals)
			{
				object target = visualPortal.Target;
				if (target != null)
				{
					try
					{
						SetPortalTollEffect(target, active: false);
					}
					catch
					{
					}
				}
			}
			VisualPortals.Clear();
		}

		private static bool HasRequiredToll(InventorySnapshot snapshot)
		{
			if (snapshot.CoreCount >= ConfiguredCoreCost)
			{
				return snapshot.EyeCount >= ConfiguredEyeCost;
			}
			return false;
		}

		private static string DescribeToll()
		{
			List<string> list = new List<string>();
			if (ConfiguredCoreCost > 0)
			{
				list.Add(ConfiguredCoreCost + " Surtling " + ((ConfiguredCoreCost == 1) ? "Core" : "Cores"));
			}
			if (ConfiguredEyeCost > 0)
			{
				list.Add(ConfiguredEyeCost + " Greydwarf " + ((ConfiguredEyeCost == 1) ? "Eye" : "Eyes"));
			}
			if (list.Count != 0)
			{
				return string.Join(" and ", list.ToArray());
			}
			return "no items";
		}

		private static object GetConnectedPortalId(object portal)
		{
			try
			{
				object obj = ReadField(portal, "m_nview");
				if (obj == null)
				{
					return null;
				}
				MethodInfo method = obj.GetType().GetMethod("GetZDO", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				object obj2 = ((method == null) ? null : method.Invoke(obj, null));
				if (obj2 == null)
				{
					return null;
				}
				MethodInfo methodInfo = obj2.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo methodInfo2)
				{
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					return methodInfo2.Name == "GetConnectionZDOID" && parameters.Length == 1 && parameters[0].ParameterType.IsEnum;
				});
				if (methodInfo == null)
				{
					return null;
				}
				object obj3 = Enum.Parse(methodInfo.GetParameters()[0].ParameterType, "Portal", ignoreCase: true);
				object obj4 = methodInfo.Invoke(obj2, new object[1] { obj3 });
				return IsNoneZdoId(obj4) ? null : obj4;
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("Could not resolve the destination portal: " + ex.Message));
				return null;
			}
		}

		private static bool IsNoneZdoId(object zdoId)
		{
			if (zdoId == null)
			{
				return true;
			}
			MethodInfo method = zdoId.GetType().GetMethod("IsNone", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method != null)
			{
				return Convert.ToBoolean(method.Invoke(zdoId, null));
			}
			return false;
		}

		private static void QueueDestinationDamage(object destinationPortalId, float damagePercent)
		{
			PendingDestinationDamage.Add(new PendingPortalDamage
			{
				ExpiresUtc = DateTime.UtcNow + PendingDamageLifetime,
				DestinationPortalId = destinationPortalId,
				DamagePercent = damagePercent
			});
			_nextDamagePollUtc = DateTime.MinValue;
		}

		internal static void UpdatePendingPortalDamage()
		{
			if (PendingDestinationDamage.Count == 0 || DateTime.UtcNow < _nextDamagePollUtc)
			{
				return;
			}
			_nextDamagePollUtc = DateTime.UtcNow + TimeSpan.FromMilliseconds(250.0);
			for (int num = PendingDestinationDamage.Count - 1; num >= 0; num--)
			{
				PendingPortalDamage pendingPortalDamage = PendingDestinationDamage[num];
				if (pendingPortalDamage.ExpiresUtc < DateTime.UtcNow)
				{
					_log.LogWarning((object)"Destination portal did not load in time; its toll damage was not applied.");
					PendingDestinationDamage.RemoveAt(num);
				}
				else
				{
					object obj = FindLoadedZNetObject(pendingPortalDamage.DestinationPortalId);
					if (obj != null && ApplyPortalDamage(obj, pendingPortalDamage.DamagePercent))
					{
						PendingDestinationDamage.RemoveAt(num);
					}
				}
			}
		}

		internal static void ClearPendingPortalDamage()
		{
			PendingDestinationDamage.Clear();
			_nextDamagePollUtc = DateTime.MinValue;
		}

		private static object FindLoadedZNetObject(object zdoId)
		{
			try
			{
				if (_zNetSceneType == null || zdoId == null)
				{
					return null;
				}
				FieldInfo field = _zNetSceneType.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				PropertyInfo property = _zNetSceneType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				object obj = ((field != null) ? field.GetValue(null) : ((property == null) ? null : property.GetValue(null, null)));
				if (obj == null)
				{
					return null;
				}
				MethodInfo methodInfo = _zNetSceneType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo method)
				{
					ParameterInfo[] parameters = method.GetParameters();
					return method.Name == "FindInstance" && parameters.Length == 1 && parameters[0].ParameterType.IsInstanceOfType(zdoId);
				});
				return (methodInfo == null) ? null : methodInfo.Invoke(obj, new object[1] { zdoId });
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("Could not look up the destination portal: " + ex.Message));
				return null;
			}
		}

		private static bool ApplyPortalDamage(object portalOrGameObject, float damagePercent)
		{
			try
			{
				object obj = FindComponent(portalOrGameObject, _wearNTearType);
				if (obj == null || _hitDataType == null)
				{
					return false;
				}
				object obj2 = ReadField(obj, "m_health");
				float num = ((obj2 == null) ? 0f : Convert.ToSingle(obj2)) * damagePercent / 100f;
				if (num <= 0f)
				{
					return true;
				}
				ConstructorInfo constructor = _hitDataType.GetConstructor(new Type[1] { typeof(float) });
				object obj3 = ((constructor == null) ? Activator.CreateInstance(_hitDataType) : constructor.Invoke(new object[1] { num }));
				if (constructor == null)
				{
					object obj4 = ReadField(obj3, "m_damage");
					FieldInfo fieldInfo = ((obj4 == null) ? null : FindField(obj4.GetType(), "m_damage"));
					if (fieldInfo == null)
					{
						return false;
					}
					fieldInfo.SetValue(obj4, num);
					FindField(obj3.GetType(), "m_damage").SetValue(obj3, obj4);
				}
				SetHitPoint(obj3, obj);
				SetEnumField(obj3, "m_hitType", "Structural");
				MethodInfo method = obj.GetType().GetMethod("Damage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { _hitDataType }, null);
				if (method == null)
				{
					return false;
				}
				method.Invoke(obj, new object[1] { obj3 });
				return true;
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not damage a toll portal: " + ex.Message));
				return false;
			}
		}

		private static object FindComponent(object instance, Type componentType)
		{
			if (instance == null || componentType == null)
			{
				return null;
			}
			if (componentType.IsInstanceOfType(instance))
			{
				return instance;
			}
			MethodInfo method = instance.GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null);
			object obj = ((method == null) ? null : method.Invoke(instance, new object[1] { componentType }));
			if (obj != null)
			{
				return obj;
			}
			PropertyInfo property = instance.GetType().GetProperty("gameObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj2 = ((property == null) ? null : property.GetValue(instance, null));
			if (obj2 == null || obj2 == instance)
			{
				return null;
			}
			return FindComponent(obj2, componentType);
		}

		private static void SetHitPoint(object hit, object component)
		{
			FieldInfo fieldInfo = FindField(hit.GetType(), "m_point");
			PropertyInfo property = component.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = ((property == null) ? null : property.GetValue(component, null));
			PropertyInfo propertyInfo = obj?.GetType().GetProperty("position", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj2 = ((propertyInfo == null) ? null : propertyInfo.GetValue(obj, null));
			if (fieldInfo != null && obj2 != null && fieldInfo.FieldType.IsInstanceOfType(obj2))
			{
				fieldInfo.SetValue(hit, obj2);
			}
		}

		private static void SetEnumField(object instance, string fieldName, string value)
		{
			FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
			if (fieldInfo != null && fieldInfo.FieldType.IsEnum)
			{
				fieldInfo.SetValue(instance, Enum.Parse(fieldInfo.FieldType, value, ignoreCase: true));
			}
		}

		private static InventorySnapshot ReadInventory(object humanoid)
		{
			try
			{
				MethodInfo method = humanoid.GetType().GetMethod("GetInventory", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				object obj = ((method == null) ? null : method.Invoke(humanoid, null));
				if (obj == null)
				{
					return null;
				}
				IEnumerable allItems = GetAllItems(obj);
				if (allItems == null)
				{
					return null;
				}
				InventorySnapshot inventorySnapshot = new InventorySnapshot
				{
					Inventory = obj
				};
				foreach (object item in allItems)
				{
					if (item == null)
					{
						continue;
					}
					int num = ReadIntField(item, "m_stack", 1);
					if (num <= 0)
					{
						continue;
					}
					if (IsSurtlingCore(item))
					{
						inventorySnapshot.CoreCount += num;
					}
					if (IsGreydwarfEye(item))
					{
						inventorySnapshot.EyeCount += num;
					}
					object obj2 = ReadField(item, "m_shared");
					if (obj2 != null)
					{
						if (ReadIntField(obj2, "m_toolTier", 0) >= 1000)
						{
							inventorySnapshot.HasAbsoluteRestriction = true;
						}
						else if (!ReadBoolField(obj2, "m_teleportable"))
						{
							inventorySnapshot.HasEligibleRestrictedItem = true;
						}
					}
				}
				return inventorySnapshot;
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not inspect player inventory: " + ex.Message));
				return null;
			}
		}

		private static IEnumerable GetAllItems(object inventory)
		{
			MethodInfo method = inventory.GetType().GetMethod("GetAllItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (!(method == null))
			{
				return method.Invoke(inventory, null) as IEnumerable;
			}
			return null;
		}

		private static bool ConsumeToll(object inventory)
		{
			try
			{
				IEnumerable allItems = GetAllItems(inventory);
				if (allItems == null)
				{
					return false;
				}
				List<object> itemList = (from object item in allItems
					where item != null
					select item).ToList();
				int configuredCoreCost = ConfiguredCoreCost;
				int configuredEyeCost = ConfiguredEyeCost;
				if (CountItems(itemList, IsSurtlingCore) < configuredCoreCost || CountItems(itemList, IsGreydwarfEye) < configuredEyeCost)
				{
					return false;
				}
				MethodInfo methodInfo = inventory.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo methodInfo2)
				{
					if (methodInfo2.Name != "RemoveItem")
					{
						return false;
					}
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					return parameters.Length == 1 && itemList.Any((object item) => parameters[0].ParameterType.IsInstanceOfType(item));
				});
				if ((configuredCoreCost > 0 || configuredEyeCost > 0) && methodInfo == null)
				{
					return false;
				}
				if (!ConsumeItemAmount(inventory, itemList, IsSurtlingCore, configuredCoreCost, methodInfo) || !ConsumeItemAmount(inventory, itemList, IsGreydwarfEye, configuredEyeCost, methodInfo))
				{
					return false;
				}
				MethodInfo method = inventory.GetType().GetMethod("Changed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				if (method != null)
				{
					method.Invoke(inventory, null);
				}
				return true;
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not consume the configured portal toll: " + ex.Message));
				return false;
			}
		}

		private static int CountItems(IEnumerable<object> items, Func<object, bool> predicate)
		{
			return items.Where(predicate).Sum((object item) => Math.Max(0, ReadIntField(item, "m_stack", 1)));
		}

		private static bool ConsumeItemAmount(object inventory, IEnumerable<object> items, Func<object, bool> predicate, int amount, MethodInfo removeItem)
		{
			int num = amount;
			foreach (object item in items.Where(predicate).ToList())
			{
				if (num <= 0)
				{
					break;
				}
				FieldInfo fieldInfo = FindField(item.GetType(), "m_stack");
				if (fieldInfo == null)
				{
					return false;
				}
				int num2 = Math.Max(0, Convert.ToInt32(fieldInfo.GetValue(item)));
				if (num2 <= num)
				{
					if (removeItem == null || !removeItem.GetParameters()[0].ParameterType.IsInstanceOfType(item))
					{
						return false;
					}
					removeItem.Invoke(inventory, new object[1] { item });
					num -= num2;
				}
				else
				{
					fieldInfo.SetValue(item, num2 - num);
					num = 0;
				}
			}
			return num == 0;
		}

		private static bool IsSurtlingCore(object item)
		{
			if (string.Equals(ReadName(ReadField(item, "m_dropPrefab")), "SurtlingCore", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			object obj = ReadField(item, "m_shared");
			string a = ((obj == null) ? null : ReadField(obj, "m_name")) as string;
			if (!string.Equals(a, "$item_surtlingcore", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(a, "Surtling core", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool IsGreydwarfEye(object item)
		{
			if (string.Equals(ReadName(ReadField(item, "m_dropPrefab")), "GreydwarfEye", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			object obj = ReadField(item, "m_shared");
			string a = ((obj == null) ? null : ReadField(obj, "m_name")) as string;
			if (!string.Equals(a, "$item_greydwarfeye", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "$item_greydwarf_eye", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(a, "Greydwarf eye", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static string ReadName(object value)
		{
			if (value == null)
			{
				return null;
			}
			PropertyInfo property = value.GetType().GetProperty("name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(property == null))
			{
				return property.GetValue(value, null) as string;
			}
			return null;
		}

		private static object ReadField(object instance, string fieldName)
		{
			if (instance == null)
			{
				return null;
			}
			FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
			if (!(fieldInfo == null))
			{
				return fieldInfo.GetValue(instance);
			}
			return null;
		}

		private static bool ReadBoolField(object instance, string fieldName)
		{
			object obj = ReadField(instance, fieldName);
			if (obj is bool)
			{
				return (bool)obj;
			}
			return false;
		}

		private static int ReadIntField(object instance, string fieldName, int fallback)
		{
			object obj = ReadField(instance, fieldName);
			if (obj != null)
			{
				return Convert.ToInt32(obj);
			}
			return fallback;
		}

		private static FieldInfo FindField(Type type, string fieldName)
		{
			while (type != null)
			{
				FieldInfo field = type.GetField(fieldName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field;
				}
				type = type.BaseType;
			}
			return null;
		}

		private static void ShowCenterMessage(object player, string text)
		{
			try
			{
				MethodInfo methodInfo = (from method in player.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where method.Name == "Message"
					select method).FirstOrDefault(delegate(MethodInfo method)
				{
					ParameterInfo[] parameters2 = method.GetParameters();
					return parameters2.Length >= 2 && parameters2[0].ParameterType.IsEnum && parameters2[1].ParameterType == typeof(string);
				});
				if (!(methodInfo == null))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					object[] array = new object[parameters.Length];
					array[0] = Enum.Parse(parameters[0].ParameterType, "Center", ignoreCase: true);
					array[1] = text;
					for (int num = 2; num < parameters.Length; num++)
					{
						array[num] = (parameters[num].HasDefaultValue ? parameters[num].DefaultValue : (parameters[num].ParameterType.IsValueType ? Activator.CreateInstance(parameters[num].ParameterType) : null));
					}
					methodInfo.Invoke(player, array);
				}
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("Could not show portal message: " + ex.Message));
			}
		}

		private static void SetPendingTrip(object player, object inventory, object sourcePortal, object destinationPortalId)
		{
			RemovePendingTrip(player);
			PendingTrips.Add(player, new PendingTrip
			{
				ExpiresUtc = DateTime.UtcNow + PendingTripLifetime,
				Inventory = inventory,
				SourcePortal = sourcePortal,
				DestinationPortalId = destinationPortalId
			});
		}

		private static PendingTrip GetPendingTrip(object player)
		{
			if (!PendingTrips.TryGetValue(player, out var value))
			{
				return null;
			}
			if (value.ExpiresUtc >= DateTime.UtcNow)
			{
				return value;
			}
			RemovePendingTrip(player);
			return null;
		}

		private static bool HasPendingTrip(object player)
		{
			return GetPendingTrip(player) != null;
		}

		private static void RemovePendingTrip(object player)
		{
			if (player != null)
			{
				PendingTrips.Remove(player);
			}
		}
	}
}