Decompiled source of BluePrinceQoL v2.4.0

Mods/BluePrinceQoL.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BluePrinceQoL;
using BluePrinceQoL.Features;
using BluePrinceQoL.Util;
using HarmonyLib;
using Il2Cpp;
using Il2CppBluePrince.Inputs;
using Il2CppBluePrince.UI;
using Il2CppCosimo.Music;
using Il2CppDG.Tweening;
using Il2CppHutongGames.PlayMaker;
using Il2CppHutongGames.PlayMaker.Actions;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityStandardAssets.Characters.FirstPerson;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "Blue Prince QoL", "2.4.0", "Lucas Pospor", null)]
[assembly: MelonGame("Dogubomb", "BLUE PRINCE")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("Lucas Pospor")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Blue Prince QoL: quality-of-life tweaks for Blue Prince (MelonLoader)")]
[assembly: AssemblyFileVersion("2.4.0.0")]
[assembly: AssemblyInformationalVersion("2.4.0+feada09773b96526ab33c2c60d0d8eb449a18246")]
[assembly: AssemblyProduct("BluePrinceQoL")]
[assembly: AssemblyTitle("BluePrinceQoL")]
[assembly: AssemblyVersion("2.4.0.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 BluePrinceQoL
{
	public class Core : MelonMod
	{
		internal static Instance Log;

		internal static MelonPreferences_Category Prefs;

		private readonly List<Feature> _features = new List<Feature>();

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			Prefs = MelonPreferences.CreateCategory("BluePrinceQoL");
			_features.Add(new StateLoggerFeature());
			_features.Add(new HudFeature());
			_features.Add(new IntroSkipFeature());
			_features.Add(new PickupSkipFeature());
			_features.Add(new TerminalFeature());
			_features.Add(new CameraZoomFeature());
			_features.Add(new MoveSpeedFeature());
			_features.Add(new EndOfDayFeature());
			_features.Add(new RunStatsFeature());
			_features.Add(new DigSpeedFeature());
			_features.Add(new StairsWarpFeature());
			_features.Add(new BootSkipFeature());
			_features.Add(new ClickUnlockFeature());
			_features.Add(new DoorKeySpeedFeature());
			_features.Add(new MusicVolumeFeature());
			_features.Add(new CursorGuardFeature());
			_features.Add(new DareGameOverFeature());
			_features.Add(new SecretGardenFeature());
			_features.Add(new ElevatorSpeedFeature());
			_features.Add(new RotundaFeature());
			_features.Add(new ModsMenuFeature());
			_features.Add(new QuitWithoutSavingFeature());
			_features.Add(new WorkshopFeature());
			_features.Add(new SecurityVideoFeature());
			_features.Add(new OfficeCompartmentFeature());
			_features.Add(new DartsEquationFeature());
			foreach (Feature feature in _features)
			{
				feature.Init();
			}
			Log.Msg($"Loaded {_features.Count} feature(s)");
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			FsmHooks.OnSceneLoaded();
			foreach (Feature f in _features)
			{
				Safe(f, delegate
				{
					f.OnSceneLoaded(sceneName);
				});
			}
		}

		public override void OnUpdate()
		{
			FsmHooks.Tick(((MelonBase)this).HarmonyInstance);
			foreach (Feature feature in _features)
			{
				if (feature.Enabled)
				{
					Safe(feature, feature.OnUpdate);
				}
			}
		}

		public override void OnLateUpdate()
		{
			foreach (Feature feature in _features)
			{
				if (feature.Enabled)
				{
					Safe(feature, feature.OnLateUpdate);
				}
			}
		}

		public override void OnGUI()
		{
			foreach (Feature feature in _features)
			{
				if (feature.Enabled)
				{
					Safe(feature, feature.OnGUI);
				}
			}
		}

		private static void Safe(Feature f, Action a)
		{
			try
			{
				a();
			}
			catch (Exception value)
			{
				f.Errors++;
				if (f.Errors <= 3)
				{
					Log.Error($"[{f.Name}] {value}");
				}
				if (f.Errors == 3)
				{
					Log.Error("[" + f.Name + "] further errors suppressed");
				}
			}
		}
	}
}
namespace BluePrinceQoL.Util
{
	internal static class FsmHooks
	{
		private const float InstallDelay = 5f;

		private static float _installAt = -1f;

		public static bool Installed { get; private set; }

		public static event Action<FsmState> BeforeEnter;

		public static event Action<FsmState> AfterEnter;

		public static event Action<Harmony> InstallPatches;

		internal static void RaiseBefore(FsmState s)
		{
			Raise(FsmHooks.BeforeEnter, s);
		}

		internal static void RaiseAfter(FsmState s)
		{
			Raise(FsmHooks.AfterEnter, s);
		}

		private static void Raise(Action<FsmState> ev, FsmState s)
		{
			if (ev == null || s == null)
			{
				return;
			}
			Delegate[] invocationList = ev.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action<FsmState> action = (Action<FsmState>)invocationList[i];
				try
				{
					action(s);
				}
				catch (Exception value)
				{
					Core.Log.Error($"FSM hook {action.Method.DeclaringType?.Name}: {value}");
				}
			}
		}

		internal static void OnSceneLoaded()
		{
			if (!Installed && _installAt < 0f)
			{
				_installAt = Time.realtimeSinceStartup + 5f;
			}
		}

		internal static void Tick(Harmony harmony)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			//IL_0071: Expected O, but got Unknown
			if (Installed || _installAt < 0f || Time.realtimeSinceStartup < _installAt)
			{
				return;
			}
			Installed = true;
			MethodInfo methodInfo = AccessTools.Method(typeof(Fsm), "EnterState", (Type[])null, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FsmHooks), "EnterStatePrefix", (Type[])null), new HarmonyMethod(typeof(FsmHooks), "EnterStatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Core.Log.Msg("FSM hooks installed");
			if (FsmHooks.InstallPatches == null)
			{
				return;
			}
			Delegate[] invocationList = FsmHooks.InstallPatches.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action<Harmony> action = (Action<Harmony>)invocationList[i];
				try
				{
					action(harmony);
				}
				catch (Exception value)
				{
					Core.Log.Error($"Patch install {action.Method.DeclaringType?.Name}: {value}");
				}
			}
		}

		private static void EnterStatePrefix(FsmState state)
		{
			RaiseBefore(state);
		}

		private static void EnterStatePostfix(FsmState state)
		{
			RaiseAfter(state);
		}

		public static string PathOf(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return "";
			}
			string text = ((Object)go).name;
			Transform parent = go.transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				text = ((Object)parent).name + "/" + text;
				parent = parent.parent;
			}
			return text;
		}
	}
	internal static class FsmUtil
	{
		public static PlayMakerFSM Get(GameObject go, string fsmName = "FSM")
		{
			if ((Object)(object)go == (Object)null)
			{
				return null;
			}
			foreach (PlayMakerFSM component in go.GetComponents<PlayMakerFSM>())
			{
				if (component.FsmName == fsmName)
				{
					return component;
				}
			}
			return null;
		}

		public static PlayMakerFSM Find(string goName, string fsmName = "FSM")
		{
			return Get(GameObject.Find(goName), fsmName);
		}

		public static FsmInt Int(PlayMakerFSM fsm, string name)
		{
			if (fsm == null)
			{
				return null;
			}
			return fsm.FsmVariables.GetFsmInt(name);
		}

		public static FsmFloat Float(PlayMakerFSM fsm, string name)
		{
			if (fsm == null)
			{
				return null;
			}
			return fsm.FsmVariables.GetFsmFloat(name);
		}

		public static FsmBool Bool(PlayMakerFSM fsm, string name)
		{
			if (fsm == null)
			{
				return null;
			}
			return fsm.FsmVariables.GetFsmBool(name);
		}

		public static FsmString String(PlayMakerFSM fsm, string name)
		{
			if (fsm == null)
			{
				return null;
			}
			return fsm.FsmVariables.GetFsmString(name);
		}
	}
	internal sealed class StateDurations
	{
		private sealed class Pending
		{
			public FsmState State;

			public readonly List<(FsmFloat f, float v)> Floats = new List<(FsmFloat, float)>();

			public readonly List<string> Log = new List<string>();
		}

		private readonly Dictionary<IntPtr, Pending> _pending = new Dictionary<IntPtr, Pending>();

		private readonly List<IntPtr> _tmp = new List<IntPtr>();

		private Pending _cur;

		public int Count => _pending.Count;

		public void OnEnter(FsmState state)
		{
			if (_pending.Count > 0 && _pending.TryGetValue(((Il2CppObjectBase)state).Pointer, out var value))
			{
				Restore(((Il2CppObjectBase)state).Pointer, value);
			}
		}

		public void Begin(FsmState state)
		{
			_cur = new Pending
			{
				State = state
			};
		}

		public void Scale(FsmFloat v, float factor, float min, string what)
		{
			if (_cur == null || !IsConst(v))
			{
				return;
			}
			float value = v.Value;
			if (value <= 0f)
			{
				return;
			}
			float num = Math.Max(value * factor, min);
			if (!(num >= value))
			{
				_cur.Floats.Add((v, value));
				v.Value = num;
				if (what != null)
				{
					_cur.Log.Add($"{what} {value:0.##}s -> {num:0.##}s");
				}
			}
		}

		public void Set(FsmFloat v, float target, string what)
		{
			if (_cur == null || !IsConst(v))
			{
				return;
			}
			float value = v.Value;
			if (!(Math.Abs(value - target) < 1E-06f))
			{
				_cur.Floats.Add((v, value));
				v.Value = target;
				if (what != null)
				{
					_cur.Log.Add($"{what} {value:0.##} -> {target:0.##}");
				}
			}
		}

		public string Commit()
		{
			Pending cur = _cur;
			_cur = null;
			if (cur == null || cur.Floats.Count == 0)
			{
				return null;
			}
			_pending[((Il2CppObjectBase)cur.State).Pointer] = cur;
			return string.Join(", ", cur.Log);
		}

		public void Tick()
		{
			if (_pending.Count == 0)
			{
				return;
			}
			_tmp.Clear();
			foreach (KeyValuePair<IntPtr, Pending> item in _pending)
			{
				bool flag;
				try
				{
					flag = item.Value.State.Active;
				}
				catch
				{
					flag = false;
				}
				if (!flag)
				{
					_tmp.Add(item.Key);
				}
			}
			foreach (IntPtr item2 in _tmp)
			{
				Restore(item2, _pending[item2]);
			}
		}

		public void RestoreAll()
		{
			if (_pending.Count == 0)
			{
				return;
			}
			_tmp.Clear();
			_tmp.AddRange(_pending.Keys);
			foreach (IntPtr item in _tmp)
			{
				Restore(item, _pending[item]);
			}
		}

		private void Restore(IntPtr ptr, Pending p)
		{
			_pending.Remove(ptr);
			foreach (var (val, value) in p.Floats)
			{
				try
				{
					val.Value = value;
				}
				catch
				{
				}
			}
		}

		private static bool IsConst(FsmFloat f)
		{
			if (f != null)
			{
				if (((NamedVariable)f).UseVariable)
				{
					return string.IsNullOrEmpty(((NamedVariable)f).Name);
				}
				return true;
			}
			return false;
		}
	}
}
namespace BluePrinceQoL.Features
{
	public class BootSkipFeature : Feature
	{
		private const string SceneName = "Main Menu";

		private const string MenuSystem = "Menu System";

		private static readonly HashSet<string> SlateStates = new HashSet<string> { "State 3", "Logo Slates", "State 6", "State 7", "State 8" };

		private const string ActivateState = "Activate Main Menu";

		private const float SlateTime = 0.05f;

		private MelonPreferences_Entry<float> _menuFade;

		private MelonPreferences_Entry<bool> _hideLogos;

		private static readonly HashSet<string> LogoObjects = new HashSet<string> { "Dogubomb", "Raw Fury" };

		public override string Name => "BootSkip";

		protected override string Description => "Shorten the logo slates at game launch";

		protected override void CreatePrefs()
		{
			_menuFade = Core.Prefs.CreateEntry<float>(Name + "_MenuFadeIn", 0.5f, "BootSkip: main menu fade-in (s)", "Fade-in of the main menu after the logos (vanilla: 1 s delay + 3 s fade).", false, false, (ValueValidator)null, (string)null);
			_hideLogos = Core.Prefs.CreateEntry<bool>(Name + "_HideLogos", true, "BootSkip: hide the logos", "Don't show the Dogubomb and Raw Fury logos at all (otherwise they flash by for a fraction of a second)", false, false, (ValueValidator)null, (string)null);
		}

		public override void OnSceneLoaded(string sceneName)
		{
			if (!base.Enabled || sceneName != "Main Menu")
			{
				return;
			}
			PlayMakerFSM val = FsmUtil.Find("Menu System");
			if ((Object)(object)val == (Object)null)
			{
				Core.Log.Warning($"[{Name}] '{"Menu System"}' FSM not found; logos unchanged");
				return;
			}
			int num = 0;
			int num2 = 0;
			float to = Mathf.Clamp(_menuFade.Value, 0.05f, 4f);
			foreach (FsmState item in (Il2CppArrayBase<FsmState>)(object)val.Fsm.States)
			{
				if (item == null)
				{
					continue;
				}
				bool flag = SlateStates.Contains(item.Name);
				bool flag2 = item.Name == "Activate Main Menu";
				if (!flag && !flag2)
				{
					continue;
				}
				foreach (FsmStateAction item2 in (Il2CppArrayBase<FsmStateAction>)(object)item.Actions)
				{
					if (item2 == null)
					{
						continue;
					}
					if (flag && _hideLogos.Value && item2.Enabled)
					{
						ActivateGameObject val2 = ((Il2CppObjectBase)item2).TryCast<ActivateGameObject>();
						if (val2 != null && val2.activate != null && val2.activate.Value)
						{
							GameObject ownerDefaultTarget = val.Fsm.GetOwnerDefaultTarget(val2.gameObject);
							if ((Object)(object)ownerDefaultTarget != (Object)null && LogoObjects.Contains(((Object)ownerDefaultTarget).name))
							{
								item2.Enabled = false;
								num2++;
								continue;
							}
						}
					}
					Wait val3 = ((Il2CppObjectBase)item2).TryCast<Wait>();
					if (val3 != null && flag && Shorten(val3.time, 0.05f))
					{
						num++;
					}
					DotweenEaseColor val4 = ((Il2CppObjectBase)item2).TryCast<DotweenEaseColor>();
					if (val4 == null)
					{
						continue;
					}
					if (flag)
					{
						if (Shorten(val4.duration, 0.05f))
						{
							num++;
						}
						if (Shorten(val4.setDelay, 0f))
						{
							num++;
						}
					}
					else
					{
						if (Shorten(val4.duration, to))
						{
							num++;
						}
						if (Shorten(val4.setDelay, 0f))
						{
							num++;
						}
					}
				}
			}
			if (num == 0)
			{
				Core.Log.Warning($"[{Name}] no logo waits/fades found on '{"Menu System"}'; logos unchanged");
			}
			else
			{
				Core.Log.Msg($"[{Name}] shortened {num} logo timings" + ((num2 > 0) ? $", logos hidden ({num2})" : ""));
			}
		}

		private static bool Shorten(FsmFloat f, float to)
		{
			if (f == null || (((NamedVariable)f).UseVariable && !string.IsNullOrEmpty(((NamedVariable)f).Name)))
			{
				return false;
			}
			if (f.Value <= to)
			{
				return false;
			}
			f.Value = to;
			return true;
		}
	}
	public class CameraZoomFeature : Feature
	{
		private enum Scope
		{
			None,
			Terminal,
			Dining,
			Utility,
			Candle,
			Safe,
			Other
		}

		private enum Kind
		{
			None,
			PlayerTween,
			PlayerOther,
			ComputerRoot,
			Sender,
			DiningFade,
			PanelDelay,
			PanelDoor,
			CandleTweens,
			SafeOpen
		}

		private sealed class Info
		{
			public Kind Kind;

			public Scope Scope;

			public IntPtr Follow;

			public Wait FollowWait;

			public IntPtr After;

			public Wait AfterWait;
		}

		private const string PlayerObject = "FPSController - Prince";

		private const string PlayerTweenState = "State 3";

		private const string ComputerRoot = "Computer System";

		private const string OverlayName = "UI OVERLAY CAM";

		private const string CamDistanceVar = "CamDistance";

		private const string PanelRoot = "FUSE BOX";

		private const string PanelCollider = "Fuse Box - First Collider";

		private const string PanelDoor = "S FuseboxDoor";

		private const float PanelResetTime = 15f;

		private const string CandleTrigger = "Trigger";

		private const string CandleRoot = "L1 Candlestick Handle";

		private const string CandlePullState = "The Pull down";

		private const string SafeOpenState = "OPEN + SPAWN";

		private const string LetterLock = "Letters Lock Secret Tunnel";

		private const float SafeMaxConstWait = 2f;

		private static readonly HashSet<string> FreezeEvents = new HashSet<string> { "CamTarget", "CamWarp", "QuickUnfreeze", "UnFreeze", "Freeze", "QuickFreeze", "InvisFreeze", "Camfreeze" };

		private const float Eps = 0.0001f;

		private const float Margin = 0.03f;

		private const float FovMin = 0.05f;

		private const float ArmTime = 1.5f;

		private MelonPreferences_Entry<float> _speed;

		private MelonPreferences_Entry<bool> _instant;

		private MelonPreferences_Entry<bool> _terminals;

		private MelonPreferences_Entry<bool> _dining;

		private MelonPreferences_Entry<bool> _eatFade;

		private MelonPreferences_Entry<bool> _utility;

		private MelonPreferences_Entry<bool> _candle;

		private MelonPreferences_Entry<bool> _safes;

		private MelonPreferences_Entry<bool> _other;

		private readonly Dictionary<IntPtr, Info> _states = new Dictionary<IntPtr, Info>();

		private readonly Dictionary<IntPtr, Scope> _fsmScope = new Dictionary<IntPtr, Scope>();

		private readonly Dictionary<IntPtr, List<(FsmFloat f, float orig)>> _restoreAfter = new Dictionary<IntPtr, List<(FsmFloat, float)>>();

		private readonly Dictionary<IntPtr, List<(FsmFloat f, float orig)>> _restoreOnEntry = new Dictionary<IntPtr, List<(FsmFloat, float)>>();

		private IntPtr _armFsm;

		private IntPtr _armFollow;

		private Wait _armWait;

		private Scope _armScope;

		private float _armUntil = -1f;

		private bool _tweenScaled;

		private float _tweenDur;

		private FsmState _pendingFollow;

		private IntPtr _armAfter;

		private Wait _armAfterWait;

		private IntPtr _resetState;

		private Wait _resetWait;

		private Scope _resetScope;

		private float _resetUntil = -1f;

		private bool _tweenActive;

		private int _syncFrames;

		private bool _warnedPlayer;

		public override string Name => "CameraZoom";

		protected override string Description => "Faster camera zoom onto terminals, the Dining Room plate, the Utility Closet fuse box and the Drawing Room safe (optionally safes, darts, maps ...)";

		protected override void CreatePrefs()
		{
			_speed = Core.Prefs.CreateEntry<float>("CameraZoom_Speed", 4f, "CameraZoom: speed", "How many times faster the zoom runs (1 = vanilla, 0 = instant)", false, false, (ValueValidator)null, (string)null);
			_instant = Core.Prefs.CreateEntry<bool>("CameraZoom_Instant", false, "CameraZoom: instant", "Skip the zoom entirely (camera jumps to the vanilla end position); overrides Speed", false, false, (ValueValidator)null, (string)null);
			_terminals = Core.Prefs.CreateEntry<bool>("CameraZoom_Terminals", true, "CameraZoom: terminals", "Computer terminals (Security, Office, Laboratory, Bomb Shelter, Grotto): zoom in/out and FOV change", false, false, (ValueValidator)null, (string)null);
			_dining = Core.Prefs.CreateEntry<bool>("CameraZoom_DiningRoom", true, "CameraZoom: Dining Room", "Dining Room dinner plate: zoom in/out", false, false, (ValueValidator)null, (string)null);
			_eatFade = Core.Prefs.CreateEntry<bool>("CameraZoom_EatFade", true, "CameraZoom: eating fade", "Also speed up the 2 s fade to black + 2 s fade back when eating in the Dining Room", false, false, (ValueValidator)null, (string)null);
			_utility = Core.Prefs.CreateEntry<bool>("CameraZoom_UtilityPanel", true, "CameraZoom: Utility Closet panel", "Utility Closet fuse box / breaker panel: zoom in/out, its door, and the waits that cover the zoom-out", false, false, (ValueValidator)null, (string)null);
			_candle = Core.Prefs.CreateEntry<bool>("CameraZoom_DrawingRoomSafe", true, "CameraZoom: Drawing Room safe", "Drawing Room candlestick: the handle pull, the zoom onto the safe and the panel sliding up to reveal it", false, false, (ValueValidator)null, (string)null);
			_safes = Core.Prefs.CreateEntry<bool>("CameraZoom_SafesAndLocks", true, "CameraZoom: safes and locks", "Wall safes (Study, Boudoir, Office, Drawing Room) and the Watch Tower letter lock: zoom in and out, and the handle/door animation after the right code, before the camera pulls back", false, false, (ValueValidator)null, (string)null);
			_other = Core.Prefs.CreateEntry<bool>("CameraZoom_OtherObjects", false, "CameraZoom: other objects", "Also speed up identical zooms elsewhere (wall safes, dartboards, Room 46 map, Lab machine, Shrine/Curse menus ...)", false, false, (ValueValidator)null, (string)null);
			FsmHooks.BeforeEnter += OnBeforeEnter;
			FsmHooks.AfterEnter += OnAfterEnter;
		}

		public override void OnSceneLoaded(string sceneName)
		{
			_states.Clear();
			_fsmScope.Clear();
			_restoreAfter.Clear();
			_restoreOnEntry.Clear();
			Disarm();
			ClearReset();
			_tweenActive = false;
			_syncFrames = 0;
		}

		private float Factor()
		{
			if (_instant.Value)
			{
				return 0f;
			}
			float value = _speed.Value;
			if (value <= 0f)
			{
				return 0f;
			}
			if (!(value <= 1f))
			{
				return 1f / value;
			}
			return 1f;
		}

		private bool Active(Scope scope)
		{
			if (!base.Enabled || Factor() >= 1f)
			{
				return false;
			}
			return scope switch
			{
				Scope.Terminal => _terminals.Value, 
				Scope.Dining => _dining.Value, 
				Scope.Utility => _utility.Value, 
				Scope.Candle => _candle.Value, 
				Scope.Safe => _safes.Value, 
				Scope.Other => _other.Value, 
				_ => false, 
			};
		}

		private void OnBeforeEnter(FsmState state)
		{
			IntPtr pointer = ((Il2CppObjectBase)state).Pointer;
			if (_restoreOnEntry.Count > 0 && _restoreOnEntry.Remove(pointer, out List<(FsmFloat, float)> value))
			{
				foreach (var (val, value2) in value)
				{
					if (val != null)
					{
						val.Value = value2;
					}
				}
			}
			if (_resetState != IntPtr.Zero && pointer == _resetState)
			{
				ApplyPanelReset(state);
			}
			Info info = InfoOf(state);
			if (info.Kind == Kind.None)
			{
				return;
			}
			switch (info.Kind)
			{
			case Kind.PlayerTween:
				ScalePlayerTween(state);
				break;
			case Kind.PlayerOther:
				if (_tweenActive)
				{
					_tweenActive = false;
					_syncFrames = 3;
				}
				break;
			case Kind.ComputerRoot:
				if (Active(Scope.Terminal))
				{
					ScaleFov(state);
				}
				break;
			case Kind.Sender:
				if (Active(info.Scope))
				{
					Arm(state, info);
					ScaleFov(state);
				}
				break;
			case Kind.DiningFade:
				if (_eatFade.Value && Active(Scope.Dining))
				{
					ScaleFades(state);
				}
				break;
			case Kind.PanelDelay:
				if (Active(Scope.Utility))
				{
					ScaleDelays(state);
				}
				break;
			case Kind.PanelDoor:
				if (Active(Scope.Utility))
				{
					ScaleDoor(state);
				}
				break;
			case Kind.CandleTweens:
				if (Active(Scope.Candle))
				{
					ScaleCandleTweens(state);
				}
				break;
			case Kind.SafeOpen:
				if (Active(Scope.Safe))
				{
					ScaleCandleTweens(state);
				}
				break;
			}
		}

		private void OnAfterEnter(FsmState state)
		{
			IntPtr pointer = ((Il2CppObjectBase)state).Pointer;
			if (_restoreAfter.Count > 0 && _restoreAfter.Remove(pointer, out List<(FsmFloat, float)> value))
			{
				foreach (var (val, value2) in value)
				{
					if (val != null)
					{
						val.Value = value2;
					}
				}
			}
			if (!(_armFollow == IntPtr.Zero) && !(pointer != _armFollow) && state.Fsm != null && !(((Il2CppObjectBase)state.Fsm).Pointer != _armFsm))
			{
				if (_tweenScaled)
				{
					ApplyFollow(state);
				}
				else
				{
					_pendingFollow = state;
				}
			}
		}

		public override void OnUpdate()
		{
			if (_syncFrames > 0)
			{
				_syncFrames--;
				Physics.SyncTransforms();
			}
			if (_armUntil >= 0f && Time.unscaledTime > _armUntil && !_tweenScaled)
			{
				Disarm();
			}
			if (_tweenScaled && _armFollow != IntPtr.Zero && Time.unscaledTime > _armUntil + 10f)
			{
				Disarm();
			}
			if (_resetUntil >= 0f && Time.unscaledTime > _resetUntil)
			{
				ClearReset();
			}
		}

		private void Arm(FsmState sender, Info info)
		{
			_armFsm = ((Il2CppObjectBase)sender.Fsm).Pointer;
			_armFollow = info.Follow;
			_armWait = info.FollowWait;
			_armScope = info.Scope;
			_armAfter = info.After;
			_armAfterWait = info.AfterWait;
			_armUntil = Time.unscaledTime + 1.5f;
			_tweenScaled = false;
			_tweenDur = 0f;
			_pendingFollow = null;
		}

		private void Disarm()
		{
			_armFsm = (_armFollow = IntPtr.Zero);
			_armWait = null;
			_armScope = Scope.None;
			_armAfter = IntPtr.Zero;
			_armAfterWait = null;
			_armUntil = -1f;
			_tweenScaled = false;
			_pendingFollow = null;
		}

		private void ScalePlayerTween(FsmState state)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Invalid comparison between Unknown and I4
			if (_armFollow == IntPtr.Zero || _tweenScaled || Time.unscaledTime > _armUntil)
			{
				return;
			}
			if (!Active(_armScope))
			{
				Disarm();
				return;
			}
			FloatOperator val = null;
			FloatClamp val2 = null;
			DotweenMoveTransformTo val3 = null;
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item != null && item.Enabled)
				{
					if (val == null)
					{
						val = ((Il2CppObjectBase)item).TryCast<FloatOperator>();
					}
					if (val2 == null)
					{
						val2 = ((Il2CppObjectBase)item).TryCast<FloatClamp>();
					}
					if (val3 == null)
					{
						val3 = ((Il2CppObjectBase)item).TryCast<DotweenMoveTransformTo>();
					}
				}
			}
			if (val == null || val2 == null || val3 == null || (int)val.operation != 2 || !IsVar(val2.floatVariable, "CamDistance") || !IsVar(val3.duration, "CamDistance") || !IsConst(val2.minValue) || !IsConst(val2.maxValue) || val.float1 == null || val.float2 == null)
			{
				if (!_warnedPlayer)
				{
					Core.Log.Warning($"[{Name}] player '{"State 3"}' doesn't look as expected; leaving zooms vanilla");
				}
				_warnedPlayer = true;
				Disarm();
				return;
			}
			float value = val2.minValue.Value;
			float value2 = val2.maxValue.Value;
			float num = Mathf.Clamp(val.float1.Value * val.float2.Value, value, value2);
			float num2 = Math.Max(num * Factor(), 0.0001f);
			AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val2.minValue, value);
			AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val2.maxValue, value2);
			val2.minValue.Value = num2;
			val2.maxValue.Value = num2;
			_tweenScaled = true;
			_tweenDur = num2;
			_tweenActive = true;
			Core.Log.Msg($"[{Name}] {_armScope} zoom {num:0.00}s -> {num2:0.00}s");
			FsmState pendingFollow = _pendingFollow;
			_pendingFollow = null;
			if (pendingFollow != null)
			{
				Fsm fsm = pendingFollow.Fsm;
				if (((fsm != null) ? fsm.ActiveState : null) != null && ((Il2CppObjectBase)pendingFollow.Fsm.ActiveState).Pointer == ((Il2CppObjectBase)pendingFollow).Pointer)
				{
					ApplyFollow(pendingFollow);
				}
			}
		}

		private void ApplyFollow(FsmState state)
		{
			Wait armWait = _armWait;
			float tweenDur = _tweenDur;
			IntPtr armAfter = _armAfter;
			Wait armAfterWait = _armAfterWait;
			Scope armScope = _armScope;
			float num = Factor();
			Disarm();
			FsmFloat val = ((armWait != null) ? armWait.time : null);
			if (val == null)
			{
				return;
			}
			float num2 = Math.Max(val.Value * num, tweenDur) + 0.03f;
			if (!(num2 >= val.Value))
			{
				if (!IsVar(val, "CamDistance"))
				{
					AddRestore(_restoreOnEntry, ((Il2CppObjectBase)state).Pointer, val, val.Value);
				}
				val.Value = num2;
				if (armAfter != IntPtr.Zero && armAfterWait != null)
				{
					_resetState = armAfter;
					_resetWait = armAfterWait;
					_resetScope = armScope;
					_resetUntil = Time.unscaledTime + 15f;
				}
			}
		}

		private void ClearReset()
		{
			_resetState = IntPtr.Zero;
			_resetWait = null;
			_resetScope = Scope.None;
			_resetUntil = -1f;
		}

		private void ApplyPanelReset(FsmState state)
		{
			Wait resetWait = _resetWait;
			FsmFloat val = ((resetWait != null) ? resetWait.time : null);
			Scope resetScope = _resetScope;
			ClearReset();
			if (IsConst(val) && Active(resetScope))
			{
				float value = val.Value;
				float num = Math.Max(value * Factor(), 0.0001f);
				if (!(num >= value))
				{
					AddRestore(_restoreOnEntry, ((Il2CppObjectBase)state).Pointer, val, value);
					val.Value = num;
				}
			}
		}

		private void ScaleDelays(FsmState state)
		{
			float num = Factor();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				SendEvent val = ((item != null) ? ((Il2CppObjectBase)item).TryCast<SendEvent>() : null);
				if (val != null && ((FsmStateAction)val).Enabled && IsConst(val.delay))
				{
					float value = val.delay.Value;
					float num2 = value * num;
					if (!(value <= 0f) && !(num2 >= value))
					{
						AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val.delay, value);
						val.delay.Value = num2;
					}
				}
			}
		}

		private void ScaleDoor(FsmState state)
		{
			float num = Factor();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				FloatClamp val = ((Il2CppObjectBase)item).TryCast<FloatClamp>();
				if (val != null && !val.everyFrame && IsConst(val.minValue) && IsConst(val.maxValue))
				{
					AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val.minValue, val.minValue.Value);
					AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val.maxValue, val.maxValue.Value);
					val.minValue.Value = Math.Max(val.minValue.Value * num, 0.0001f);
					val.maxValue.Value = Math.Max(val.maxValue.Value * num, 0.0001f);
					continue;
				}
				DotweenRotateTransformTo val2 = ((Il2CppObjectBase)item).TryCast<DotweenRotateTransformTo>();
				if (val2 != null && IsConst(val2.duration))
				{
					float value = val2.duration.Value;
					float num2 = Math.Max(value * num, 0.0001f);
					if (!(num2 >= value))
					{
						AddRestore(_restoreOnEntry, ((Il2CppObjectBase)state).Pointer, val2.duration, value);
						val2.duration.Value = num2;
					}
				}
			}
		}

		private void ScaleCandleTweens(FsmState state)
		{
			float num = Factor();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				FsmFloat val = null;
				FsmFloat val2 = null;
				FsmBool val3 = null;
				DotweenRotateTransformTo val4 = ((Il2CppObjectBase)item).TryCast<DotweenRotateTransformTo>();
				if (val4 != null)
				{
					val = val4.duration;
					val2 = val4.setDelay;
					val3 = val4.isSpeedBased;
				}
				else
				{
					DotweenMoveTransformTo val5 = ((Il2CppObjectBase)item).TryCast<DotweenMoveTransformTo>();
					if (val5 == null)
					{
						continue;
					}
					val = val5.duration;
					val2 = val5.setDelay;
					val3 = val5.isSpeedBased;
				}
				if (val3 != null && !((NamedVariable)val3).IsNone && val3.Value)
				{
					continue;
				}
				FsmFloat[] array = (FsmFloat[])(object)new FsmFloat[2] { val, val2 };
				foreach (FsmFloat val6 in array)
				{
					if (IsConst(val6) && !(val6.Value <= 0f))
					{
						AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val6, val6.Value);
						val6.Value = Math.Max(val6.Value * num, (val6 == val2) ? 0f : 0.0001f);
					}
				}
			}
		}

		private void ScaleFov(FsmState state)
		{
			float num = Factor();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				EnableFOVOffsetAction val = ((item != null) ? ((Il2CppObjectBase)item).TryCast<EnableFOVOffsetAction>() : null);
				if (val != null && ((FsmStateAction)val).Enabled && IsConst(val.FadeTime))
				{
					float value = val.FadeTime.Value;
					float num2 = Math.Max(value * num, 0.05f);
					if (!(num2 >= value))
					{
						AddRestore(_restoreAfter, ((Il2CppObjectBase)state).Pointer, val.FadeTime, value);
						val.FadeTime.Value = num2;
					}
				}
			}
		}

		private void ScaleFades(FsmState state)
		{
			float num = Factor();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				CameraFadeOut obj = ((Il2CppObjectBase)item).TryCast<CameraFadeOut>();
				object obj2 = ((obj != null) ? obj.time : null);
				if (obj2 == null)
				{
					CameraFadeIn obj3 = ((Il2CppObjectBase)item).TryCast<CameraFadeIn>();
					obj2 = ((obj3 != null) ? obj3.time : null);
				}
				FsmFloat val = (FsmFloat)obj2;
				if (IsConst(val))
				{
					float value = val.Value;
					float num2 = Math.Max(value * num, 0.0001f);
					if (!(num2 >= value))
					{
						AddRestore(_restoreOnEntry, ((Il2CppObjectBase)state).Pointer, val, value);
						val.Value = num2;
					}
				}
			}
		}

		private Info InfoOf(FsmState state)
		{
			if (_states.TryGetValue(((Il2CppObjectBase)state).Pointer, out var value))
			{
				return value;
			}
			value = Classify(state) ?? new Info();
			_states[((Il2CppObjectBase)state).Pointer] = value;
			return value;
		}

		private Info Classify(FsmState state)
		{
			Fsm fsm = state.Fsm;
			GameObject val = ((fsm != null) ? fsm.GameObject : null);
			if ((Object)(object)val == (Object)null || state.Actions == null)
			{
				return null;
			}
			string name = ((Object)val).name;
			if (name == "FPSController - Prince")
			{
				if (fsm.Name != "FSM")
				{
					return null;
				}
				return new Info
				{
					Kind = ((state.Name == "State 3") ? Kind.PlayerTween : Kind.PlayerOther)
				};
			}
			if (name == "Computer System")
			{
				Transform parent = val.transform.parent;
				if ((Object)(object)parent == (Object)null || ((Object)parent).name != "UI OVERLAY CAM")
				{
					return null;
				}
				if (!HasAction<EnableFOVOffsetAction>(state))
				{
					return null;
				}
				return new Info
				{
					Kind = Kind.ComputerRoot
				};
			}
			if (SendsCamTarget(state))
			{
				Scope scope = ScopeOf(fsm, val);
				(FsmState, Wait) tuple = FollowOf(state, scope);
				if (tuple.Item1 == null)
				{
					return null;
				}
				Info info = new Info
				{
					Kind = Kind.Sender,
					Scope = scope,
					Follow = ((Il2CppObjectBase)tuple.Item1).Pointer,
					FollowWait = tuple.Item2
				};
				if ((scope == Scope.Utility && IsConst(tuple.Item2.time)) || scope == Scope.Safe)
				{
					(FsmState, Wait) tuple2 = PanelResetOf(tuple.Item1, scope);
					if (tuple2.Item1 != null)
					{
						info.After = ((Il2CppObjectBase)tuple2.Item1).Pointer;
						info.AfterWait = tuple2.Item2;
					}
				}
				return info;
			}
			if ((HasAction<CameraFadeOut>(state) || HasAction<CameraFadeIn>(state)) && ScopeOf(fsm, val) == Scope.Dining)
			{
				return new Info
				{
					Kind = Kind.DiningFade,
					Scope = Scope.Dining
				};
			}
			if (name == "Fuse Box - First Collider" && ScopeOf(fsm, val) == Scope.Utility && HasDelayedSend(state))
			{
				return new Info
				{
					Kind = Kind.PanelDelay,
					Scope = Scope.Utility
				};
			}
			if (name.StartsWith("Digits", StringComparison.Ordinal) && fsm.Name == "FSM" && state.Name == "OPEN + SPAWN" && ScopeOf(fsm, val) == Scope.Safe)
			{
				return new Info
				{
					Kind = Kind.SafeOpen,
					Scope = Scope.Safe
				};
			}
			if (name == "Trigger" && IsUnder(val, "L1 Candlestick Handle") && fsm.Name == "FSM" && (state.Name == "The Pull down" || FollowIsCandleZoom(state)))
			{
				return new Info
				{
					Kind = Kind.CandleTweens,
					Scope = Scope.Candle
				};
			}
			if (name == "S FuseboxDoor" && IsUnder(val, "FUSE BOX") && (HasAction<FloatClamp>(state) || HasAction<DotweenRotateTransformTo>(state)))
			{
				return new Info
				{
					Kind = Kind.PanelDoor,
					Scope = Scope.Utility
				};
			}
			return null;
		}

		private static bool FollowIsCandleZoom(FsmState state)
		{
			Fsm fsm = state.Fsm;
			if (((fsm != null) ? fsm.States : null) == null)
			{
				return false;
			}
			foreach (FsmState item in (Il2CppArrayBase<FsmState>)(object)state.Fsm.States)
			{
				if (((item != null) ? item.Actions : null) != null && SendsCamTarget(item))
				{
					FsmState obj = FinishedTarget(item);
					if (((obj != null) ? new IntPtr?(((Il2CppObjectBase)obj).Pointer) : ((IntPtr?)null)) == ((Il2CppObjectBase)state).Pointer)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool HasDelayedSend(FsmState state)
		{
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				SendEvent val = ((item != null) ? ((Il2CppObjectBase)item).TryCast<SendEvent>() : null);
				if (val != null && ((FsmStateAction)val).Enabled && IsConst(val.delay) && val.delay.Value > 0f)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsUnder(GameObject go, string parentName)
		{
			Transform parent = go.transform.parent;
			if ((Object)(object)parent != (Object)null)
			{
				return ((Object)parent).name == parentName;
			}
			return false;
		}

		private static (FsmState state, Wait wait) PanelResetOf(FsmState follow, Scope scope)
		{
			FsmState val = FinishedTarget(follow);
			if (((val != null) ? val.Actions : null) == null)
			{
				return default((FsmState, Wait));
			}
			Wait val2 = null;
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)val.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				Wait val3 = ((Il2CppObjectBase)item).TryCast<Wait>();
				if (val3 != null)
				{
					if (val2 != null || val3.realTime)
					{
						return default((FsmState, Wait));
					}
					val2 = val3;
				}
				else
				{
					if (((Il2CppObjectBase)item).TryCast<SetFsmInt>() != null || ((Il2CppObjectBase)item).TryCast<SetProperty>() != null)
					{
						continue;
					}
					if (scope == Scope.Safe && val2 != null && val.IsSequence)
					{
						SendEvent val4 = ((Il2CppObjectBase)item).TryCast<SendEvent>();
						if (val4 != null && !val4.everyFrame)
						{
							FsmEvent sendEvent = val4.sendEvent;
							if (((sendEvent != null) ? sendEvent.Name : null) == "QuickUnfreeze" && IsConst(val4.delay) && val4.delay.Value <= 0f)
							{
								continue;
							}
						}
					}
					return default((FsmState, Wait));
				}
			}
			if (!IsConst((val2 != null) ? val2.time : null))
			{
				return default((FsmState, Wait));
			}
			return (state: val, wait: val2);
		}

		private static FsmState FinishedTarget(FsmState state)
		{
			if (state.Transitions == null)
			{
				return null;
			}
			foreach (FsmTransition item in (Il2CppArrayBase<FsmTransition>)(object)state.Transitions)
			{
				if (item != null && item.EventName == "FINISHED")
				{
					return string.IsNullOrEmpty(item.ToState) ? null : state.Fsm.GetState(item.ToState);
				}
			}
			return null;
		}

		private static bool SendsCamTarget(FsmState state)
		{
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				SendEvent val = ((item != null) ? ((Il2CppObjectBase)item).TryCast<SendEvent>() : null);
				if (val != null && ((FsmStateAction)val).Enabled)
				{
					FsmEvent sendEvent = val.sendEvent;
					if (((sendEvent != null) ? sendEvent.Name : null) == "CamTarget")
					{
						return true;
					}
				}
			}
			return false;
		}

		private Scope ScopeOf(Fsm fsm, GameObject go)
		{
			if (_fsmScope.TryGetValue(((Il2CppObjectBase)fsm).Pointer, out var value))
			{
				return value;
			}
			value = Scope.Other;
			string name = ((Object)go).name;
			if (name == "Click Trigger" && OpensTerminal(fsm))
			{
				value = Scope.Terminal;
			}
			else if (name == "Fuse Box - First Collider" && IsUnder(go, "FUSE BOX"))
			{
				value = Scope.Utility;
			}
			else if (name == "Trigger" && IsUnder(go, "L1 Candlestick Handle"))
			{
				value = Scope.Candle;
			}
			else if (IsSafe(go))
			{
				value = Scope.Safe;
			}
			else if ((name == "C - Dining Room Dishes" || name == "Course Trigger" || (name.StartsWith("C - ", StringComparison.Ordinal) && name.EndsWith("Dish Click", StringComparison.Ordinal))) && FsmHooks.PathOf(go).Contains("Dining Room"))
			{
				value = Scope.Dining;
			}
			_fsmScope[((Il2CppObjectBase)fsm).Pointer] = value;
			return value;
		}

		private static bool IsSafe(GameObject go)
		{
			string name = ((Object)go).name;
			if (name == "Letters Lock Secret Tunnel")
			{
				return true;
			}
			Transform parent = go.transform.parent;
			Transform val = (name.StartsWith("Digits", StringComparison.Ordinal) ? go.transform : ((name == "First Collider" && (Object)(object)parent != (Object)null && ((Object)parent).name.StartsWith("Digits", StringComparison.Ordinal)) ? parent : null));
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			return FsmHooks.PathOf(((Component)val).gameObject).Contains("Safe", StringComparison.OrdinalIgnoreCase);
		}

		private static bool OpensTerminal(Fsm fsm)
		{
			FsmState state = fsm.GetState("Wait for Exit 2");
			if (((state != null) ? state.Actions : null) == null)
			{
				return false;
			}
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				GetChild val = ((item != null) ? ((Il2CppObjectBase)item).TryCast<GetChild>() : null);
				if (((val != null) ? val.childName : null) != null && val.childName.Value == "Computer System")
				{
					return true;
				}
			}
			return false;
		}

		private static (FsmState state, Wait wait) FollowOf(FsmState sender, Scope scope)
		{
			FsmState val = FinishedTarget(sender);
			if (((val != null) ? val.Actions : null) == null)
			{
				return default((FsmState, Wait));
			}
			Wait val2 = null;
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)val.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				Wait val3 = ((Il2CppObjectBase)item).TryCast<Wait>();
				if (val3 != null)
				{
					if (val2 != null || val3.realTime)
					{
						return default((FsmState, Wait));
					}
					val2 = val3;
					continue;
				}
				FloatOperator val4 = ((Il2CppObjectBase)item).TryCast<FloatOperator>();
				if (val4 != null)
				{
					if (!val4.everyFrame)
					{
						continue;
					}
					return default((FsmState, Wait));
				}
				FloatClamp val5 = ((Il2CppObjectBase)item).TryCast<FloatClamp>();
				if (val5 != null)
				{
					if (val5.everyFrame)
					{
						return default((FsmState, Wait));
					}
				}
				else
				{
					if (((Il2CppObjectBase)item).TryCast<SetFsmInt>() != null)
					{
						continue;
					}
					if (scope == Scope.Utility)
					{
						SendEvent val6 = ((Il2CppObjectBase)item).TryCast<SendEvent>();
						if (val6 != null && !val6.everyFrame)
						{
							FsmEvent sendEvent = val6.sendEvent;
							if (((sendEvent != null) ? sendEvent.Name : null) != "CamTarget")
							{
								continue;
							}
						}
					}
					if (scope == Scope.Safe)
					{
						SendEvent val7 = ((Il2CppObjectBase)item).TryCast<SendEvent>();
						if (val7 != null && !val7.everyFrame && IsConst(val7.delay) && val7.delay.Value <= 0f)
						{
							HashSet<string> freezeEvents = FreezeEvents;
							FsmEvent sendEvent2 = val7.sendEvent;
							if (!freezeEvents.Contains(((sendEvent2 != null) ? sendEvent2.Name : null) ?? ""))
							{
								continue;
							}
						}
					}
					if (scope != Scope.Candle || (((Il2CppObjectBase)item).TryCast<ActivateGameObject>() == null && ((Il2CppObjectBase)item).TryCast<DotweenMoveTransformTo>() == null))
					{
						return default((FsmState, Wait));
					}
				}
			}
			if (((val2 != null) ? val2.time : null) == null)
			{
				return default((FsmState, Wait));
			}
			if (IsVar(val2.time, "CamDistance"))
			{
				return (state: val, wait: val2);
			}
			if ((scope == Scope.Dining || scope == Scope.Utility) && IsConst(val2.time))
			{
				return (state: val, wait: val2);
			}
			if (scope == Scope.Safe && IsConst(val2.time) && val2.time.Value <= 2f)
			{
				return (state: val, wait: val2);
			}
			return default((FsmState, Wait));
		}

		private static bool HasAction<T>(FsmState state) where T : FsmStateAction
		{
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item != null && item.Enabled && ((Il2CppObjectBase)item).TryCast<T>() != null)
				{
					return true;
				}
			}
			return false;
		}

		private static void AddRestore(Dictionary<IntPtr, List<(FsmFloat, float)>> d, IntPtr state, FsmFloat f, float orig)
		{
			if (!d.TryGetValue(state, out var value))
			{
				value = (d[state] = new List<(FsmFloat, float)>());
			}
			foreach (var item in value)
			{
				if (((Il2CppObjectBase)item.Item1).Pointer == ((Il2CppObjectBase)f).Pointer)
				{
					return;
				}
			}
			value.Add((f, orig));
		}

		private static bool IsConst(FsmFloat f)
		{
			if (f != null)
			{
				if (((NamedVariable)f).UseVariable)
				{
					return string.IsNullOrEmpty(((NamedVariable)f).Name);
				}
				return true;
			}
			return false;
		}

		private static bool IsVar(FsmFloat f, string name)
		{
			if (f != null && ((NamedVariable)f).UseVariable)
			{
				return ((NamedVariable)f).Name == name;
			}
			return false;
		}
	}
	public class ClickUnlockFeature : Feature
	{
		private enum Kind
		{
			None,
			Player,
			Gm
		}

		private sealed class Info
		{
			public Kind Kind;

			public Wait Wait;
		}

		private sealed class Pending
		{
			public Kind Kind;

			public Fsm Fsm;

			public FsmState State;

			public Wait Wait;

			public int Frame;

			public float EntryTime;

			public bool Released;

			public int ReleasedFrame;

			public float Vanilla;

			public FsmFloat Shortened;

			public float ShortenedOrig;

			public bool Blocked;
		}

		private const string PlayerObject = "FPSController - Prince";

		private const string PlayerPath = "__SYSTEM/FPS Home/FPSController - Prince";

		private const string GmObject = "Global Manager";

		private const string DraftUiPath = "__SYSTEM/THE DRAFT/anchor/DRAFT UI";

		private const string DraftIdleState = "Listener";

		private const string PlayerDoneState = "State 7";

		private const string GmDoneState = "State 41";

		private const string UnfreezeState = "UNFROZEN 2";

		private const string FadeUnfreezeState = "UNFROZEN 3";

		private const string QuickUnfreezeState = "UNFROZEN";

		private static readonly HashSet<string> GmWaitStates = new HashSet<string> { "State 40", "State 42", "State 43" };

		private static readonly HashSet<string> PlayerUnlockedStates = new HashSet<string> { "State 7", "State 14", "State 17" };

		private const int MinFrames = 2;

		private const int FallbackFrames = 5;

		private const float FallbackTime = 0.0001f;

		private MelonPreferences_Entry<float> _delay;

		private MelonPreferences_Entry<bool> _afterDrafts;

		private MelonPreferences_Entry<bool> _quick;

		private MelonPreferences_Entry<bool> _crosshair;

		private MelonPreferences_Entry<bool> _log;

		private readonly Dictionary<IntPtr, Info> _states = new Dictionary<IntPtr, Info>();

		private Pending _player;

		private Pending _gm;

		private Fsm _playerFsm;

		private Fsm _draftFsm;

		private float _nextDraftSearch;

		private bool _warnedDraft;

		private bool _warnedShape;

		private bool _warnedFallback;

		private int _releases;

		public override string Name => "ClickUnlock";

		protected override string Description => "Shorter click lock-out after closing cards, menus and documents (camera/crosshair come back sooner)";

		protected override void CreatePrefs()
		{
			_delay = Core.Prefs.CreateEntry<float>("ClickUnlock_Delay", 0.1f, "ClickUnlock: delay", "Seconds after an unfreeze before you can click the world again (vanilla: 0.99 after cards/menus, 0.6 after drafts). Never less than two frames, whatever the value.", false, false, (ValueValidator)null, (string)null);
			_afterDrafts = Core.Prefs.CreateEntry<bool>("ClickUnlock_AfterDrafts", true, "ClickUnlock: after drafts", "Also shorten the 0.4 s lock after walking through a freshly drafted door (still waits for the draft screen to reset)", false, false, (ValueValidator)null, (string)null);
			_quick = Core.Prefs.CreateEntry<bool>("ClickUnlock_AfterZooms", true, "ClickUnlock: after zooms", "Also shorten the 0.1 s after a quick unfreeze (leaving a safe, terminal or other zoomed-in object) to two frames, so the crosshair comes back as soon as the camera is back", false, false, (ValueValidator)null, (string)null);
			_crosshair = Core.Prefs.CreateEntry<bool>("ClickUnlock_Crosshair", true, "ClickUnlock: crosshair", "Also bring the crosshair back as soon as clicking works again (Global Manager cursor waits: 1.1 s cards, 0.8 s dig cards, 2 s drafts)", false, false, (ValueValidator)null, (string)null);
			_log = Core.Prefs.CreateEntry<bool>("ClickUnlock_Log", false, "ClickUnlock: log", "Log every shortened lock and why one is held back (for testing; the first 3 are always logged)", false, false, (ValueValidator)null, (string)null);
			FsmHooks.BeforeEnter += OnBeforeEnter;
			FsmHooks.AfterEnter += OnAfterEnter;
		}

		public override void OnSceneLoaded(string sceneName)
		{
			RestoreFallback(_player);
			RestoreFallback(_gm);
			_player = (_gm = null);
			_states.Clear();
			_playerFsm = null;
			_draftFsm = null;
			_nextDraftSearch = 0f;
		}

		private float Delay()
		{
			float value = _delay.Value;
			if (!float.IsNaN(value) && !(value < 0f))
			{
				return value;
			}
			return 0f;
		}

		private void OnBeforeEnter(FsmState state)
		{
			if (_player?.Shortened != null && ((Il2CppObjectBase)state).Pointer == ((Il2CppObjectBase)_player.State).Pointer)
			{
				RestoreFallback(_player);
				_player = null;
			}
			if (_gm?.Shortened != null && ((Il2CppObjectBase)state).Pointer == ((Il2CppObjectBase)_gm.State).Pointer)
			{
				RestoreFallback(_gm);
				_gm = null;
			}
		}

		private void OnAfterEnter(FsmState state)
		{
			string name = state.Name;
			switch (name)
			{
			default:
				if (!PlayerUnlockedStates.Contains(name) && !GmWaitStates.Contains(name))
				{
					return;
				}
				break;
			case "UNFROZEN 2":
			case "UNFROZEN 3":
			case "UNFROZEN":
				break;
			}
			Info info = InfoOf(state);
			if (info.Kind == Kind.Player)
			{
				_playerFsm = state.Fsm;
			}
			if (info.Wait != null && base.Enabled && (info.Kind != Kind.Player || !(name == "UNFROZEN 3") || _afterDrafts.Value) && (info.Kind != Kind.Player || !(name == "UNFROZEN") || _quick.Value) && (info.Kind != Kind.Gm || _crosshair.Value))
			{
				Pending obj = new Pending
				{
					Kind = info.Kind,
					Fsm = state.Fsm,
					State = state,
					Wait = info.Wait,
					Frame = Time.frameCount,
					EntryTime = Time.time
				};
				FsmFloat time = info.Wait.time;
				obj.Vanilla = ((time != null) ? time.Value : 0f);
				Pending pending = obj;
				if (info.Kind == Kind.Player)
				{
					RestoreFallback(_player);
					_player = pending;
				}
				else
				{
					RestoreFallback(_gm);
					_gm = pending;
				}
			}
		}

		public override void OnUpdate()
		{
			_player = Step(_player);
			_gm = Step(_gm);
		}

		private Pending Step(Pending p)
		{
			if (p == null)
			{
				return null;
			}
			Fsm fsm = p.Fsm;
			FsmState val = ((fsm != null) ? fsm.ActiveState : null);
			if (val == null || ((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)p.State).Pointer)
			{
				RestoreFallback(p);
				return null;
			}
			int frameCount = Time.frameCount;
			if (p.Released)
			{
				if (p.Shortened == null && frameCount > p.ReleasedFrame + 5)
				{
					ApplyFallback(p);
				}
				return p;
			}
			if (frameCount - p.Frame < 2)
			{
				return p;
			}
			if (p.State.Name != "UNFROZEN" && Time.time - p.EntryTime < Delay())
			{
				return p;
			}
			Wait wait = p.Wait;
			FsmFloat time = wait.time;
			if (time == null || wait.timer >= time.Value)
			{
				return p;
			}
			string text = Gate(p);
			if (text != null)
			{
				if (_log.Value && !p.Blocked)
				{
					Core.Log.Msg($"[{Name}] holding '{p.State.Name}' ({text})");
				}
				p.Blocked = true;
				return p;
			}
			float value = Time.time - p.EntryTime;
			wait.timer = time.Value;
			p.Released = true;
			p.ReleasedFrame = frameCount;
			if (_log.Value || _releases < 3)
			{
				Core.Log.Msg($"[{Name}] {Label(p)}: {p.Vanilla:0.00}s -> {value:0.00}s");
			}
			_releases++;
			return p;
		}

		private string Gate(Pending p)
		{
			Fsm val = DraftFsm();
			if (val == null)
			{
				return "DRAFT UI not found";
			}
			string activeStateName = val.ActiveStateName;
			if (activeStateName != "Listener")
			{
				return "DRAFT UI in '" + activeStateName + "'";
			}
			if (p.Kind == Kind.Gm)
			{
				Fsm obj = _playerFsm;
				if (obj == null)
				{
					PlayMakerFSM obj2 = FsmUtil.Find("__SYSTEM/FPS Home/FPSController - Prince");
					obj = (_playerFsm = ((obj2 != null) ? obj2.Fsm : null));
				}
				Fsm val2 = obj;
				if (val2 == null)
				{
					return "player FSM not found";
				}
				string activeStateName2 = val2.ActiveStateName;
				if (activeStateName2 == null || !PlayerUnlockedStates.Contains(activeStateName2))
				{
					return "player in '" + activeStateName2 + "'";
				}
				FsmVariables variables = val2.Variables;
				FsmBool val3 = ((variables != null) ? variables.GetFsmBool("currently frozen") : null);
				if (val3 != null && val3.Value)
				{
					return "player frozen";
				}
			}
			return null;
		}

		private void ApplyFallback(Pending p)
		{
			FsmFloat time = p.Wait.time;
			if (time != null && (!((NamedVariable)time).UseVariable || string.IsNullOrEmpty(((NamedVariable)time).Name)))
			{
				if (!_warnedFallback)
				{
					Core.Log.Warning($"[{Name}] '{p.State.Name}' didn't finish after its timer was fast-forwarded; shortening its time instead");
				}
				_warnedFallback = true;
				p.Shortened = time;
				p.ShortenedOrig = time.Value;
				time.Value = 0.0001f;
			}
		}

		private static void RestoreFallback(Pending p)
		{
			if (p?.Shortened != null)
			{
				if (Math.Abs(p.Shortened.Value - 0.0001f) < 1E-07f)
				{
					p.Shortened.Value = p.ShortenedOrig;
				}
				p.Shortened = null;
			}
		}

		private Fsm DraftFsm()
		{
			if (_draftFsm != null)
			{
				return _draftFsm;
			}
			if (Time.unscaledTime < _nextDraftSearch)
			{
				return null;
			}
			_nextDraftSearch = Time.unscaledTime + 1f;
			PlayMakerFSM obj = FsmUtil.Find("__SYSTEM/THE DRAFT/anchor/DRAFT UI");
			_draftFsm = ((obj != null) ? obj.Fsm : null);
			if (_draftFsm == null && !_warnedDraft)
			{
				_warnedDraft = true;
				Core.Log.Warning($"[{Name}] '{"__SYSTEM/THE DRAFT/anchor/DRAFT UI"}' not found; click locks stay vanilla until it is");
			}
			return _draftFsm;
		}

		private static string Label(Pending p)
		{
			if (p.Kind != Kind.Player)
			{
				return "crosshair (" + p.State.Name + ")";
			}
			if (!(p.State.Name == "UNFROZEN 3"))
			{
				if (!(p.State.Name == "UNFROZEN"))
				{
					return "unlock after unfreeze";
				}
				return "unlock after zoom";
			}
			return "unlock after draft";
		}

		private Info InfoOf(FsmState state)
		{
			if (_states.TryGetValue(((Il2CppObjectBase)state).Pointer, out var value))
			{
				return value;
			}
			value = Classify(state) ?? new Info();
			_states[((Il2CppObjectBase)state).Pointer] = value;
			return value;
		}

		private Info Classify(FsmState state)
		{
			Fsm fsm = state.Fsm;
			GameObject val = ((fsm != null) ? fsm.GameObject : null);
			if ((Object)(object)val == (Object)null || fsm.Name != "FSM")
			{
				return null;
			}
			string name = state.Name;
			if (((Object)val).name == "FPSController - Prince")
			{
				if (FsmHooks.PathOf(val) != "__SYSTEM/FPS Home/FPSController - Prince")
				{
					return null;
				}
				if (name != "UNFROZEN 2" && name != "UNFROZEN 3" && name != "UNFROZEN")
				{
					return new Info
					{
						Kind = Kind.Player
					};
				}
				Wait val2 = SoleWait(state, "State 7");
				if (val2 == null)
				{
					WarnShape("player '" + name + "'");
				}
				return new Info
				{
					Kind = Kind.Player,
					Wait = val2
				};
			}
			if (((Object)val).name == "Global Manager" && GmWaitStates.Contains(name))
			{
				Wait val3 = SoleWait(state, "State 41");
				if (val3 != null && !OnlySetFsmInt(fsm.GetState("State 41")))
				{
					val3 = null;
				}
				if (val3 == null)
				{
					WarnShape("Global Manager '" + name + "'");
					return null;
				}
				return new Info
				{
					Kind = Kind.Gm,
					Wait = val3
				};
			}
			return null;
		}

		private static Wait SoleWait(FsmState state, string next)
		{
			if (state.Actions == null || state.Transitions == null)
			{
				return null;
			}
			string text = null;
			foreach (FsmTransition item in (Il2CppArrayBase<FsmTransition>)(object)state.Transitions)
			{
				if (item != null && item.EventName == "FINISHED")
				{
					text = item.ToState;
					break;
				}
			}
			if (text != next)
			{
				return null;
			}
			Wait val = null;
			foreach (FsmStateAction item2 in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item2 == null || !item2.Enabled)
				{
					continue;
				}
				Wait val2 = ((Il2CppObjectBase)item2).TryCast<Wait>();
				if (val2 != null)
				{
					if (val != null || val2.realTime || val2.time == null)
					{
						return null;
					}
					if (val2.finishEvent == null || val2.finishEvent.Name != "FINISHED")
					{
						return null;
					}
					val = val2;
				}
			}
			return val;
		}

		private static bool OnlySetFsmInt(FsmState s)
		{
			if (((s != null) ? s.Actions : null) == null)
			{
				return false;
			}
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)s.Actions)
			{
				if (item != null && item.Enabled && ((Il2CppObjectBase)item).TryCast<SetFsmInt>() == null)
				{
					return false;
				}
			}
			return true;
		}

		private void WarnShape(string what)
		{
			if (!_warnedShape)
			{
				_warnedShape = true;
				Core.Log.Warning($"[{Name}] {what} doesn't look as expected; leaving that wait vanilla");
			}
		}
	}
	public class CursorGuardFeature : Feature
	{
		private sealed class Plan
		{
			public FsmState State;

			public List<SetFsmInt> Hides;
		}

		private sealed class Pending
		{
			public FsmState State;

			public Fsm Fsm;

			public readonly List<FsmInt> Values = new List<FsmInt>();
		}

		private const string PlayerPath = "__SYSTEM/FPS Home/FPSController - Prince";

		private const string PlayerObject = "FPSController - Prince";

		private const string PlayerCamera = "FirstPersonCharacter";

		private const string GmObject = "Global Manager";

		private const string CursorVar = "NewCursor";

		private const int Hidden = 0;

		private const int Crosshair = 2;

		private static readonly HashSet<string> PlayerFreeStates = new HashSet<string> { "State 1", "State 7", "State 14", "State 17" };

		private static readonly HashSet<string> FreezeEvents = new HashSet<string> { "Freeze", "QuickFreeze", "InvisFreeze", "Freeze No Retract", "PumpFreeze", "Camfreeze", "disablemove", "disablemovefoundation", "CamTarget", "CamWarp" };

		private static readonly HashSet<string> PlayerOnlyFreezeEvents = new HashSet<string> { "disable", "enable", "Update" };

		private static readonly HashSet<string> ExcludingActions = new HashSet<string> { "SetMainCamera", "CameraFadeOut", "CameraFadeIn", "SetMouseCursor2" };

		private MelonPreferences_Entry<bool> _log;

		private MelonPreferences_Entry<bool> _mainMenu;

		private const string MenuMouseManager = "MOUSE MANAGER";

		private const string MenuIdleState = "Cursor Check";

		private const int MenuPointer = 1;

		private float _menuUntil = -1f;

		private static readonly Plan Nothing = new Plan();

		private readonly Dictionary<IntPtr, Plan> _plans = new Dictionary<IntPtr, Plan>();

		private readonly Dictionary<IntPtr, bool> _fsmExcluded = new Dictionary<IntPtr, bool>();

		private readonly Dictionary<IntPtr, Pending> _pending = new Dictionary<IntPtr, Pending>();

		private readonly List<IntPtr> _tmp = new List<IntPtr>();

		private Fsm _playerFsm;

		private float _nextPlayerSearch;

		private int _swaps;

		private bool _warnedPlan;

		public override string Name => "CursorGuard";

		protected override string Description => "Keep the crosshair visible while you can move (objects that hide it during their own animation, e.g. Parlor box keyholes)";

		protected override void CreatePrefs()
		{
			_mainMenu = Core.Prefs.CreateEntry<bool>("CursorGuard_MainMenu", true, "CursorGuard: main menu pointer", "Show the mouse pointer as soon as the main menu loads (the game hides it until the menu has faded in)", false, false, (ValueValidator)null, (string)null);
			_log = Core.Prefs.CreateEntry<bool>("CursorGuard_Log", false, "CursorGuard: log", "Log every hidden-cursor write that is turned into the crosshair (the first 3 are always logged)", false, false, (ValueValidator)null, (string)null);
			FsmHooks.BeforeEnter += OnBeforeEnter;
		}

		public override void OnSceneLoaded(string sceneName)
		{
			RestoreAll();
			_plans.Clear();
			_fsmExcluded.Clear();
			_playerFsm = null;
			_nextPlayerSearch = 0f;
			_menuUntil = ((sceneName == "Main Menu") ? (Time.unscaledTime + 15f) : (-1f));
		}

		private void OnBeforeEnter(FsmState state)
		{
			IntPtr pointer = ((Il2CppObjectBase)state).Pointer;
			if (_pending.Count > 0 && _pending.TryGetValue(pointer, out var value))
			{
				Restore(pointer, value);
			}
			if (!base.Enabled)
			{
				if (_pending.Count > 0)
				{
					RestoreAll();
				}
				return;
			}
			if (!_plans.TryGetValue(pointer, out var value2))
			{
				try
				{
					value2 = BuildPlan(state);
				}
				catch (Exception ex)
				{
					value2 = Nothing;
					if (!_warnedPlan)
					{
						Core.Log.Warning($"[{Name}] could not analyse state '{state.Name}': {ex.Message}");
					}
					_warnedPlan = true;
				}
				_plans[pointer] = value2;
			}
			if (value2 == Nothing)
			{
				return;
			}
			Fsm fsm = state.Fsm;
			if (fsm == null || !PlayerFree())
			{
				return;
			}
			Pending pending = null;
			foreach (SetFsmInt hide in value2.Hides)
			{
				if (!((FsmStateAction)hide).Enabled)
				{
					continue;
				}
				FsmInt setValue = hide.setValue;
				if (setValue == null || setValue.Value != 0)
				{
					continue;
				}
				GameObject ownerDefaultTarget = fsm.GetOwnerDefaultTarget(hide.gameObject);
				if (!((Object)(object)ownerDefaultTarget == (Object)null) && !(((Object)ownerDefaultTarget).name != "Global Manager"))
				{
					setValue.Value = 2;
					object obj = pending;
					if (obj == null)
					{
						obj = new Pending
						{
							State = state,
							Fsm = fsm
						};
						pending = (Pending)obj;
					}
					((Pending)obj).Values.Add(setValue);
				}
			}
			if (pending != null)
			{
				_pending[pointer] = pending;
				if (_log.Value || _swaps < 3)
				{
					Core.Log.Msg($"[{Name}] keeping the crosshair: {FsmHooks.PathOf(fsm.GameObject)} :: {fsm.Name} :: {state.Name}");
				}
				_swaps++;
			}
		}

		public override void OnUpdate()
		{
			if (_menuUntil >= 0f)
			{
				MenuPointerOnce();
			}
			if (_pending.Count == 0)
			{
				return;
			}
			bool flag = PlayerFree();
			_tmp.Clear();
			foreach (KeyValuePair<IntPtr, Pending> item in _pending)
			{
				Pending value = item.Value;
				Fsm fsm = value.Fsm;
				FsmState val = ((fsm != null) ? fsm.ActiveState : null);
				if (!flag || val == null || ((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)value.State).Pointer)
				{
					_tmp.Add(item.Key);
				}
			}
			foreach (IntPtr item2 in _tmp)
			{
				Restore(item2, _pending[item2]);
			}
		}

		private void MenuPointerOnce()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!_mainMenu.Value || Time.unscaledTime > _menuUntil)
			{
				_menuUntil = -1f;
				return;
			}
			GameObject go = null;
			Scene activeScene = SceneManager.GetActiveScene();
			foreach (GameObject item in (Il2CppArrayBase<GameObject>)(object)((Scene)(ref activeScene)).GetRootGameObjects())
			{
				if ((Object)(object)item != (Object)null && ((Object)item).name == "MOUSE MANAGER")
				{
					go = item;
					break;
				}
			}
			PlayMakerFSM val = FsmUtil.Get(go);
			if ((Object)(object)val == (Object)null || val.ActiveStateName != "Cursor Check")
			{
				return;
			}
			_menuUntil = -1f;
			FsmInt val2 = FsmUtil.Int(val, "NewCursor");
			FsmInt val3 = FsmUtil.Int(val, "CurrentCursor");
			if (val2 != null && val3 != null && val2.Value == 0 && val3.Value == 0)
			{
				val2.Value = 1;
				if (_log.Value)
				{
					Core.Log.Msg("[" + Name + "] main menu pointer shown at once");
				}
			}
		}

		private void Restore(IntPtr ptr, Pending p)
		{
			_pending.Remove(ptr);
			foreach (FsmInt value in p.Values)
			{
				try
				{
					if (value.Value == 2)
					{
						value.Value = 0;
					}
				}
				catch
				{
				}
			}
		}

		private void RestoreAll()
		{
			if (_pending.Count == 0)
			{
				return;
			}
			_tmp.Clear();
			_tmp.AddRange(_pending.Keys);
			foreach (IntPtr item in _tmp)
			{
				Restore(item, _pending[item]);
			}
		}

		private bool PlayerFree()
		{
			Fsm val = PlayerFsm();
			if (val == null)
			{
				return false;
			}
			string activeStateName = val.ActiveStateName;
			if (activeStateName == null || !PlayerFreeStates.Contains(activeStateName))
			{
				return false;
			}
			FsmVariables variables = val.Variables;
			FsmBool val2 = ((variables != null) ? variables.GetFsmBool("currently frozen") : null);
			if (val2 == null || val2.Value)
			{
				return false;
			}
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null)
			{
				return ((Object)((Component)main).gameObject).name == "FirstPersonCharacter";
			}
			return false;
		}

		private Fsm PlayerFsm()
		{
			if (_playerFsm != null)
			{
				return _playerFsm;
			}
			if (Time.unscaledTime < _nextPlayerSearch)
			{
				return null;
			}
			_nextPlayerSearch = Time.unscaledTime + 1f;
			PlayMakerFSM obj = FsmUtil.Find("__SYSTEM/FPS Home/FPSController - Prince");
			return _playerFsm = ((obj != null) ? obj.Fsm : null);
		}

		private Plan BuildPlan(FsmState state)
		{
			Fsm fsm = state.Fsm;
			Il2CppReferenceArray<FsmStateAction> actions = state.Actions;
			if (fsm == null || actions == null)
			{
				return Nothing;
			}
			List<SetFsmInt> list = null;
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				SetFsmInt val = ((Il2CppObjectBase)item).TryCast<SetFsmInt>();
				if (val == null)
				{
					continue;
				}
				FsmString variableName = val.variableName;
				if (!(((variableName != null) ? variableName.Value : null) != "NewCursor") && (val.fsmName == null || string.IsNullOrEmpty(val.fsmName.Value) || !(val.fsmName.Value != "FSM")))
				{
					FsmInt setValue = val.setValue;
					if (setValue != null && (!((NamedVariable)setValue).UseVariable || string.IsNullOrEmpty(((NamedVariable)setValue).Name)) && setValue.Value == 0)
					{
						(list ?? (list = new List<SetFsmInt>())).Add(val);
					}
				}
			}
			if (list == null || FsmExcluded(fsm))
			{
				return Nothing;
			}
			return new Plan
			{
				State = state,
				Hides = list
			};
		}

		private bool FsmExcluded(Fsm fsm)
		{
			IntPtr pointer = ((Il2CppObjectBase)fsm).Pointer;
			if (_fsmExcluded.TryGetValue(pointer, out var value))
			{
				return value;
			}
			value = ComputeExcluded(fsm);
			_fsmExcluded[pointer] = value;
			return value;
		}

		private static bool ComputeExcluded(Fsm fsm)
		{
			GameObject gameObject = fsm.GameObject;
			if ((Object)(object)gameObject == (Object)null)
			{
				return true;
			}
			if (((Object)gameObject).name == "FPSController - Prince" || ((Object)gameObject).name == "Global Manager" || ((Object)gameObject).name == "DRAFT UI")
			{
				return true;
			}
			Transform val = gameObject.transform;
			while ((Object)(object)val != (Object)null)
			{
				if (((Object)val).name == "UI OVERLAY CAM")
				{
					return true;
				}
				val = val.parent;
			}
			Il2CppReferenceArray<FsmState> states = fsm.States;
			if (states == null)
			{
				return true;
			}
			foreach (FsmState item in (Il2CppArrayBase<FsmState>)(object)states)
			{
				if (((item != null) ? item.Actions : null) == null)
				{
					continue;
				}
				foreach (FsmStateAction item2 in (Il2CppArrayBase<FsmStateAction>)(object)item.Actions)
				{
					if (item2 == null || !item2.Enabled)
					{
						continue;
					}
					string name = ((MemberInfo)((Object)item2).GetIl2CppType()).Name;
					if (ExcludingActions.Contains(name))
					{
						return true;
					}
					if (name != "SendEvent")
					{
						continue;
					}
					SendEvent val2 = ((Il2CppObjectBase)item2).Cast<SendEvent>();
					FsmEvent sendEvent = val2.sendEvent;
					string text = ((sendEvent != null) ? sendEvent.Name : null);
					if (text != null)
					{
						if (FreezeEvents.Contains(text))
						{
							return true;
						}
						if (PlayerOnlyFreezeEvents.Contains(text) && MaybePlayer(fsm, val2.eventTarget))
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		private static bool MaybePlayer(Fsm fsm, FsmEventTarget et)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Invalid comparison between Unknown and I4
			if (et == null)
			{
				return false;
			}
			if ((int)et.target == 4)
			{
				return true;
			}
			if ((int)et.target != 1 && (int)et.target != 2)
			{
				return false;
			}
			GameObject ownerDefaultTarget;
			try
			{
				ownerDefaultTarget = fsm.GetOwnerDefaultTarget(et.gameObject);
			}
			catch
			{
				return true;
			}
			if (!((Object)(object)ownerDefaultTarget == (Object)null))
			{
				return ((Object)ownerDefaultTarget).name.StartsWith("FPSController", StringComparison.Ordinal);
			}
			return true;
		}
	}
	public class DareGameOverFeature : Feature
	{
		private enum Kind
		{
			None,
			GameOverStart,
			MusicFade,
			MenuWait,
			MenuFadeUp,
			MenuReady,
			MenuExit
		}

		private sealed class Pending
		{
			public FsmState State;

			public readonly List<(FsmFloat f, float v)> Floats = new List<(FsmFloat, float)>();
		}

		private const string GameOverObject = "GAME OVER";

		private const string DareEngineObject = "DARE ENGINE";

		private const string MenuObject = "Main Menu";

		private const string SelectorObject = "Selector";

		private const string FadeUpObject = "Fade up";

		private const string WaitForFreeObject = "Wait for Free";

		private const string VoPath = "DAY/Music/gameoverVO";

		private const float FastFade = 1f;

		private const float MinDur = 0.05f;

		private const float SkipScale = 10f;

		private const float SkipGrace = 0.75f;

		private const float SkipTimeout = 15f;

		private const float VoFadeTime = 0.5f;

		private MelonPreferences_Entry<float> _menuDelay;

		private MelonPreferences_Entry<string> _skipKeysPref;

		private MelonPreferences_Entry<bool> _autoSkip;

		private readonly List<KeyCode> _skipKeys = new List<KeyCode>();

		private readonly Dictionary<IntPtr, Kind> _kinds = new Dictionary<IntPtr, Kind>();

		private readonly Dictionary<IntPtr, Pending> _pending = new Dictionary<IntPtr, Pending>();

		private readonly List<IntPtr> _tmp = new List<IntPtr>();

		private GameObject _gameOver;

		private float _gameOverAt;

		private bool _menuReached;

		private bool _skipping;

		private float _skipAt;

		private float _savedScale;

		private bool _muteVo;

		private AudioSource _vo;

		public override string Name => "DareGameOver";

		protected override string Description => "Shorter Dare mode Game Over scene: Main Menu button sooner, and a key to fast-forward the poem";

		protected override void CreatePrefs()
		{
			_menuDelay = Core.Prefs.CreateEntry<float>("DareGameOver_MenuDelay", 3f, "DareGameOver: menu delay", "Seconds after the Game Over screen appears before the Main Menu button shows (vanilla: 10)", false, false, (ValueValidator)null, (string)null);
			_skipKeysPref = Core.Prefs.CreateEntry<string>("DareGameOver_SkipKeys", "Space,Return,Escape,Mouse0", "DareGameOver: skip keys", "Keys that fast-forward the poem to the Game Over screen (UnityEngine.KeyCode names, comma separated; Mouse0 = left click). Empty = no skip key", false, false, (ValueValidator)null, (string)null);
			_autoSkip = Core.Prefs.CreateEntry<bool>("DareGameOver_AutoSkip", false, "DareGameOver: auto skip", "Always fast-forward the poem, without pressing a key", false, false, (ValueValidator)null, (string)null);
			ParseKeys();
			((MelonEventBase<LemonAction<string, string>>)(object)_skipKeysPref.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				ParseKeys();
			}, 0, false);
			FsmHooks.BeforeEnter += OnBeforeEnter;
			FsmHooks.AfterEnter += OnAfterEnter;
		}

		private void ParseKeys()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			_skipKeys.Clear();
			string[] array = (_skipKeysPref.Value ?? "").Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				if (Enum.TryParse<KeyCode>(text.Trim(), ignoreCase: true, out KeyCode result) && (int)result != 0)
				{
					_skipKeys.Add(result);
					continue;
				}
				Core.Log.Warning($"[{Name}] unknown key '{text}' in DareGameOver_SkipKeys");
			}
		}

		public override void OnSceneLoaded(string sceneName)
		{
			StopSkip("scene load");
			RestoreAll();
			_kinds.Clear();
			_gameOver = null;
			_menuReached = false;
			_muteVo = false;
			_vo = null;
		}

		private void OnBeforeEnter(FsmState state)
		{
			IntPtr pointer = ((Il2CppObjectBase)state).Pointer;
			if (_pending.Count > 0)
			{
				if (_pending.TryGetValue(pointer, out var value))
				{
					Restore(pointer, value);
				}
				if (!base.Enabled)
				{
					RestoreAll();
				}
			}
			if (!_kinds.TryGetValue(pointer, out var value2))
			{
				value2 = Classify(state);
				_kinds[pointer] = value2;
			}
			if (value2 == Kind.None || !base.Enabled)
			{
				return;
			}
			switch (value2)
			{
			case Kind.GameOverStart:
			{
				Fsm fsm = state.Fsm;
				_gameOver = ((fsm != null) ? fsm.GameObject : null);
				_gameOverAt = Time.realtimeSinceStartup;
				_menuReached = false;
				Core.Log.Msg("[" + Name + "] Game Over scene started");
				if (_autoSkip.Value)
				{
					StartSkip();
				}
				return;
			}
			case Kind.MenuReady:
				return;
			}
			Pending pending = new Pending
			{
				State = state
			};
			List<string> list = new List<string>();
			foreach (FsmStateAction item in (Il2CppArrayBase<FsmStateAction>)(object)state.Actions)
			{
				if (item == null || !item.Enabled)
				{
					continue;
				}
				switch (value2)
				{
				case Kind.MusicFade:
				{
					Wait val4 = ((Il2CppObjectBase)item).TryCast<Wait>();
					if (val4 != null)
					{
						Shorten(pending, val4.time, 1f, "Wait", list);
						break;
					}
					TrackFadeToVolumeAction val5 = ((Il2CppObjectBase)item).TryCast<TrackFadeToVolumeAction>();
					if (val5 != null)
					{
						Shorten(pending, val5.fadeTime, 1f, "music fade", list);
					}
					break;
				}
				case Kind.MenuWait:
				{
					Wait val6 = ((Il2CppObjectBase)item).TryCast<Wait>();
					if (val6 != null)
					{
						Shorten(pending, val6.time, Math.Max(_menuDelay.Value, 0.05f), "Wait", list);
					}
					break;
				}
				case Kind.MenuFadeUp:
				{
					DotweenEaseColor val3 = ((Il2CppObjectBase)item).TryCast<DotweenEaseColor>();
					if (val3 != null)
					{
						Shorten(pending, val3.duration, 1f, "fade up", list);
					}
					break;
				}
				case Kind.MenuExit:
				{
					CameraFadeOut val = ((Il2CppObjectBase)item).TryCast<CameraFadeOut>();
					if (val != null)
					{
						Shorten(pending, val.time, 1f, "FadeOut", list);
						break;
					}
					DotweenAudioMixer val2 = ((Il2CppObjectBase)item).TryCast<DotweenAudioMixer>();
					if (val2 != null)
					{
						Shorten(pending, val2.duration, 1f, "Audio", list);
					}
					break;
				}
				}
			}
			if (pending.Floats.Count != 0)
			{
				_pending[pointer] = pending;
				Instance log = Core.Log;
				DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(8, 4);
				defaultInterpolatedStringHandler.AppendLiteral("[");
				defaultInterpolatedStringHandler.AppendFormatted(Name);
				defaultInterpolatedStringHandler.AppendLiteral("] ");
				Fsm fsm2 = state.Fsm;
				object value3;
				if (fsm2 == null)
				{
					value3 = null;
				}
				else
				{
					GameObject gameObject = fsm2.GameObject;
					value3 = ((gameObject != null) ? ((Object)gameObject).name : null);
				}
				defaultInterpolatedStringHandler.AppendFormatted((string?)value3);
				defaultInterpolatedStringHandler.AppendLiteral(" / ");
				defaultInterpolatedStringHandler.AppendFormatted(state.Name);
				defaultInterpolatedStringHandler.AppendLiteral(": ");
				defaultInterpolatedStringHandler.AppendFormatted(string.Join(", ", list));
				log.Msg(defaultInterpolatedStringHandler.ToStringAndClear());
			}
		}

		private void OnAfterEnter(FsmState state)
		{
			if (_kinds.TryGetValue(((Il2CppObjectBase)state).Pointer, out var value) && value == Kind.MenuReady)
			{
				_menuReached = true;
				StopSkip("Main Menu button ready");
			}
		}

		public override void OnUpdate()
		{
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			if (_pending.Count > 0)
			{
				_tmp.Clear();
				foreach (KeyValuePair<IntPtr, Pending> item in _pending)
				{
					bool flag;
					try
					{
						flag = item.Value.State.Active;
					}
					catch
					{
						flag = false;
					}
					if (!flag)
					{
						_tmp.Add(item.Key);
					}
				}
				foreach (IntPtr item2 in _tmp)
				{
					Restore(item2, _pending[item2]);
				}
			}
			if (_muteVo)
			{
				FadeVo();
			}
			bool flag2 = (Object)(object)_gameOver != (Object)null && _gameOver.activeInHierarchy;
			if (_skipping)
			{
				if (!flag2)
				{
					StopSkip("Game Over scene ended");
				}
				else if (Time.realtimeSinceStartup - _skipAt > 15f)
				{
					StopSkip("timeout");
				}
			}
			else
			{
				if (!flag2 || _menuReached || _skipKeys.Count == 0 || Time.realtimeSinceStartup - _gameOverAt < 0.75f)
				{
					return;
				}
				foreach (KeyCode skipKey in _skipKeys)
				{
					if (Input.GetKeyDown(skipKey))
					{
						StartSkip();
						break;
					}
				}
			}
		}

		private void StartSkip()
		{
			if (!_skipping && !_menuReached)
			{
				_savedScale = Time.timeScale;
				Time.timeScale = 10f;
				_skipping = true;
				_skipAt = Time.realtimeSinceStartup;
				_muteVo = true;
				Core.Log.Msg("[" + Name + "] fast-forwarding to the Game Over screen");
			}
		}

		private void StopSkip(string why)
		{
			if (_skipping)
			{
				_skipping = false;
				if (Mathf.Approximately(Time.timeScale, 10f))
				{
					Time.timeScale = _savedScale;
				}
				Core.Log.Msg($"[{Name}] fast-forward stopped ({why}) after {Time.realtimeSinceStartup - _skipAt:0.0}s");
			}
		}

		private void FadeVo()
		{
			if ((Object)(object)_vo == (Object)null)
			{
				GameObject val = GameObject.Find("DAY/Music/gameoverVO");
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				_vo = val.GetComponent<AudioSource>();
				if ((Object)(object)_vo == (Object)null)
				{
					_muteVo = false;
					return;
				}
			}
			if (_vo.isPlaying)
			{
				_vo.volume = Mathf.MoveTowards(_vo.volume, 0f, Time.unscaledDeltaTime * 0.24f / 0.5f);
				if (_vo.volume <= 0f)
				{
					_vo.Stop();
				}
			}
		}

		private static void Shorten(Pending pend, FsmFloat v, float target, string what, List<string> log)
		{
			if (IsConst(v))
			{
				float value = v.Value;
				if (!(value <= target))
				{
					pend.Floats.Add((v, value));
					v.Value = target;
					log.Add($"{what} {value:0.##}s -> {target:0.##}s");
				}
			}
		}

		private void Restore(IntPtr ptr, Pending p)
		{
			_pending.Remove(ptr);
			foreach (var (val, value) in p.Floats)
			{
				try
				{
					val.Value = value;
				}
				catch
				{
				}
			}
		}

		private void RestoreAll()
		{
			if (_pending.Count == 0)
			{
				return;
			}
			_tmp.Clear();
			_tmp.AddRange(_pending.Keys);
			foreach (IntPtr item in _tmp)
			{
				Restore(item, _pending[item]);
			}
		}

		private static Kind Classify(FsmState state)
		{
			Fsm fsm = state.Fsm;
			if (fsm == null || fsm.Name != "FSM")
			{
				return Kind.None;
			}
			GameObject gameObject = fsm.GameObject;
			if ((Object)(object)gameObject == (Object)null)
			{
				return Kind.None;
			}
			Transform parent = gameObject.transform.parent;
			string text = (((Object)(object)parent != (Object)null) ? ((Object)parent).name : null);
			string name = state.Name;
			switch (((Object)gameObject).name)
			{
			case "GAME OVER":
				if (text == "DARE ENGINE")
				{
					if (!(name == "pre fade 7"))
					{
						return Kind.None;
					}
					return Kind.GameOverStart;
				}
				break;
			case "Wait for Free":
				if (text == "DARE ENGINE")
				{
					if (!(name == "State 4"))
					{
						return Kind.None;
					}
					return Kind.MusicFade;
				}
				break;
			case "Main Menu":
				if (text == "Selector")
				{
					return name switch
					{
						"State 1" => Kind.MenuWait, 
						"State 3" => Kind.MenuReady, 
						"State 6" => Kind.MenuExit, 
						_ => Kind.None, 
					};
				}
				break;
			case "Fade up":
				if (text == "Main Menu")
				{
					if (!(name == "State 2"))
					{
						return Kind.None;
					}
					return Kind.MenuFadeUp;
				}
				break;
			}
			return Kind.None;
		}

		private static bool IsConst(FsmFloat f)
		{
			if (f != null)
			{
				if (((NamedVariable)f).UseVariable)
				{
					return string.IsNullOrEmpty(((NamedVariable)f).Name);
				}
				return true;
			}
			return false;
		}
	}
	public class DartsEquationFeature : Feature
	{
		private readonly struct Sector
		{
			public readonly int Number;

			public readonly char Ring;

			public readonly string Type;

			public Sector(int n, char ring, string type)
			{
				Number = n;
				Ring = ring;
				Type = type;
			}
		}

		private const string Collider = "Darts - First Collider";

		private const string BoardPath = "Dartboard New";

		private static readonly Regex SectorRe = new Regex("^(\\d+)([A-Z])(.+)$");

		private MelonPreferences_Entry<float> _fontScale;

		private MelonPreferences_Entry<float> _posY;

		private MelonPreferences_Entry<string> _fontPref;

		private MelonPreferences_Entry<bool> _redOdd;

		private const string RampProp = "_RampColor2";

		private static readonly Color OddTint = new Color(1f, 0.7f, 0.7f, 1f);

		private readonly List<Renderer> _numbers = new List<Renderer>();

		private readonly HashSet<IntPtr> _numberSeen = new HashSet<IntPtr>();

		private bool _tinted;

		private MaterialPropertyBlock _block;

		private GameObject _collider;

		private DartsStageManager _board;

		private string _raw;

		private string _text = "";

		private GameObject _root;

		private TextMeshProUGUI _tmp;

		private RectTransform _panel;

		private string _layoutText;

		private float _layoutSize;

		private float _layoutWidth;

		private int _layoutLines = 1;

		private bool _gaveUp;

		private bool _menuCheckBroken;

		private int _attempts;

		private float _nextTry;

		private string _shownText;

		private string _times = "×";

		private string _divide = "÷";

		private string _colAdd = "#76CEE5";

		private string _colSub = "#FFF51E";

		private string _colMul = "#B47097";

		private string _colDiv = "#7E69C5";

		private const string ColCrossed = "#9A9A9A";

		private static readonly Dictionary<string, string[]> Shapes = new Dictionary<string, string[]>
		{
			["S"] = new string[1] { "sq" },
			["D"] = new string[1] { "sq4" },
			["V"] = new string[1] { "rev" },
			["O"] = new string[1] { "r1" },
			["T"] = new string[1] { "r10" },
			["H"] = new string[1] { "r100" },
			["W"] = new string[2] { "sq", "rev" },
			["Q"] = new string[2] { "rev", "sq" },
			["1"] = new string[2] { "r1", "sq" },
			["2"] = new string[2] { "r10", "sq" },
			["3"] = new string[2] { "r100", "sq" },
			["4"] = new string[2] { "rev", "r1" },
			["5"] = new string[2] { "rev", "r10" },
			["6"] = new string[2] { "rev", "r100" },
			["7"] = new string[2] { "r1", "rev" },
			["8"] = new string[2] { "r10", "rev" },
			["9"] = new string[2] { "r100", "rev" },
			["A"] = new string[2] { "sq", "r1" },
			["B"] = new string[2] { "sq", "r10" },
			["C"] = new string[2] { "sq", "r100" }
		};

		public override string Name => "DartsEquation";

		protected override string Description => "Shows the Billiard Room dartboard puzzle as an equation at the bottom of the screen, and tints the board's odd numbers red";

		protected override bool DefaultEnabled => false;

		protected override void CreatePrefs()
		{
			MelonPreferences_Category prefs = Core.Prefs;
			_fontScale = prefs.CreateEntry<float>(Name + "_FontScale", 0.045f, "DartsEquation: font scale", "Text size as a fraction of screen height.", false, false, (ValueValidator)null, (string)null);
			_posY = prefs.CreateEntry<float>(Name + "_PosY", 0.935f, "DartsEquation: position", "Vertical position of the equation as a fraction of the screen height from the top.", false, false, (ValueValidator)null, (string)null);
			_fontPref = prefs.CreateEntry<string>(Name + "_Font", "TypeNeat SDF", "DartsEquation: font", "A game TextMeshPro font asset name. The default has the × and ÷ signs; other fonts fall back to x and /.", false, false, (ValueValidator)null, (string)null);
			_redOdd = prefs.CreateEntry<bool>(Name + "_RedOddNumbers", true, "DartsEquation: red odd numbers", "Tints the odd numbers around the Billiard Room dartboard slightly red.", false, false, (ValueValidator)null, (string)null);
			FsmHooks.AfterEnter += OnAfterEnter;
		}

		public override void OnSceneLoaded(string sceneName)
		{
			_collider = null;
			_board = null;
			_raw = null;
			_numbers.Clear();
			_numberSeen.Clear();
			_tinted = false;
		}

		private void OnAfterEnter(FsmState state)
		{
			Fsm fsm = state.Fsm;
			GameObject val = ((fsm != null) ? fsm.GameObject : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			if (state.Fsm.Name == "Billiards")
			{
				SeeNumber(val);
			}
			else if (!(((Object)val).name != "Darts - First Collider") && !(state.Fsm.Name != "FSM"))
			{
				string name = state.Name;
				bool flag = ((name == "Camera Move 2" || name == "Wait for Exit 2") ? true : false);
				bool flag2 = flag;
				if (flag2 && (Object)(object)_collider != (Object)(object)val)
				{
					_board = null;
					_raw = null;
				}
				_collider = (flag2 ? val : null);
			}
		}

		private void SeeNumber(GameObject go)
		{
			if (!_numberSeen.Add(((Il2CppObjectBase)go).Pointer))
			{
				return;
			}
			Transform parent = go.transform.parent;
			if ((Object)(object)parent == (Object)null || (Object)(object)parent.parent == (Object)null || ((Object)parent.parent).name != "Dartboard System" || !((Object)parent).name.EndsWith("holder", StringComparison.Ordinal) || !int.TryParse(((Object)go).name, out var result) || result % 2 == 0)
			{
				return;
			}
			Renderer component = go.GetComponent<Renderer>();
			if (!((Object)(object)component == (Object)null))
			{
				_numbers.Add(component);
				if (_tinted)
				{
					Tint(component, on: true);
				}
			}
		}

		private void SyncTint()
		{
			bool flag = base.Enabled && _redOdd.Value;
			if (flag == _tinted)
			{
				return;
			}
			_tinted = flag;
			foreach (Renderer number in _numbers)
			{
				Tint(number, flag);
			}
		}

		private void Tint(Renderer r, bool on)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)r == (Object)null))
				{
					if (_block == null)
					{
						_block = new MaterialPropertyBlock();
					}
					r.GetPropertyBlock(_block);
					Material sharedMaterial = r.sharedMaterial;
					Color val = (((Object)(object)sharedMaterial != (Object)null && sharedMaterial.HasProperty("_RampColor2")) ? sharedMaterial.GetColor("_RampColor2") : Color.white);
					_block.SetColor("_RampColor2", on ? (val * OddTint) : val);
					r.SetPropertyBlock(_block);
				}
			}
			catch (Exception ex)
			{
				Core.Log.Warning("[" + Name + "] can't tint a dartboard number: " + ex.Message);
			}
		}

		public override void OnUpdate()
		{
			SyncTint();
			bool show = base.Enabled && (Object)(object)_collider != (Object)null && _collider.activeInHierarchy && !MenuOpen() && UpdateText();
			Sync(show);
		}

		private bool UpdateText()
		{
			if ((Object)(object)_board == (Object)null)
			{
				Transform val = (((Object)(object)_collider.transform.parent != (Object)null) ? _collider.transform.parent.Find("Dartboard New") : null);
				_board = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<DartsStageManager>() : null);
				if ((Object)(object)_board == (Object)null)
				{
					return false;
				}
				ReadColors();
				Transform parent = _collider.transform.parent;
				for (int i = 0; i < parent.childCount; i++)
				{
					Transform child = parent.GetChild(i);
					if (child.childCount > 0 && ((Object)child).name.EndsWith("holder", StringComparison.Ordinal))
					{
						SeeNumber(((Component)child.GetChild(0)).gameObject);
					}
				}
			}
			DartsPuzzleData currentStage = _board.CurrentStage;
			string text = ((currentStage != null) ? currentStage.RawString : null);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			if (text != _raw)
			{
				_raw = text;
				try
				{
					_text = Build(text);
				}
				catch (Exception ex)
				{
					_text = "";
					Core.Log.Warning($"[{Name}] can't read dart puzzle '{text}': {ex.Message}");
				}
			}
			return _text.Length > 0;
		}

		private void ReadColors()
		{
			try
			{
				DartsColorData val = (((Object)(object)_board.TargetGraphics != (Object)null) ? _board.TargetGraphics.DartsColors : null);
				if (!((Object)(object)val == (Object)null))
				{
					_colAdd = Hex(val.Color_Add, _colAdd);
					_colSub = Hex(val.Color_Subtract, _colSub);
					_colMul = Hex(val.Color_Multiply, _colMul);
					_colDiv = Hex(val.Color_Divide, _colDiv);
				}
			}
			catch (Exception ex)
			{
				Core.Log.Warning("[" + Name + "] darts colours not found, using defaults: " + ex.Message);
			}
		}

		private static string Hex(ColorData d, string fallback)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)d == (Object)null)
			{
				return fallback;
			}
			Color displayColor = d.DisplayColor;
			return $"#{Byte(displayColor.r):X2}{Byte(displayColor.g):X2}{Byte(displayColor.b):X2}";
			static int Byte(float v)
			{
				return Mathf.Clamp(Mathf.RoundToInt(v * 255f), 0, 255);
			}
		}

		private string Build(string raw)
		{
			string[] array = raw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
			if (array.Length < 2)
			{
				return "";
			}
			string text = array[0];
			char c = ((text.Length >= 3) ? text[2] : 'B');
			string shape = ((text.Length >= 4) ? text.Substring(3) : "E");
			Dictionary<int, string> dictionary = new Dictionary<int, string>();
			SortedDictionary<char, List<Sector>> sortedDictionary = new SortedDictionary<char, List<Sector>>();
			for (int i = 1; i < array.Length; i++)
			{
				Match match = SectorRe.Match(array[i]);
				if (!match.Success)
				{
					throw new FormatException("sector '" + array[i] + "'");
				}
				Sector item = new Sector(int.Parse(match.Groups[1].Value), match.Groups[2].Value[0], match.Groups[3].Value);
				if (item.Ring == 'E')
				{
					dictionary[item.Number] = item.Type;
					continue;
				}
				if (!sortedDictionary.TryGetValue(item.Ring, out var value))
				{
					value = (sortedDictionary[item.Ring] = new List<Sector>());
				}
				value.Add(item);
			}
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = true;
			foreach (List<Sector> value5 in sortedDictionary.Values)
			{
				bool flag2 = false;
				foreach (Sector item2 in value5)
				{
					var (value2, value3, c2) = Op(item2.Type);
					dictionary.TryGetValue(item2.Number, out var value4);
					string text2 = Number(item2.Number, value4);
					string type = item2.Type;
					if ((type == "3" || type == "1") ? true : false)
					{
						text2 += "/3";
					}
					StringBuilder stringBuilder2 = new StringBuilder();
					bool flag3 = flag;
					if (flag3)
					{
						bool flag4 = ((c2 == 'D' || c2 == 'M') ? true : false);
						flag3 = flag4;
					}
					if (flag3)
					{
						stringBuilder2.Append("0 ");
					}
					if (!flag)
					{
						stringBuilder2.Append(' ');
					}
					StringBuilder stringBuilder3 = stringBuilder2;
					StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(16, 2, stringBuilder3);
					handler.AppendLiteral("<color=");
					handler.AppendFormatted(value3);
					handler.AppendLiteral(">");
					handler.AppendFormatted(value2);
					handler.AppendLiteral("</color>");
					stringBuilder3.Append(ref handler);
					if (!flag)
					{
						stringBuilder2.Append(' ');
					}
					stringBuilder2.Append(text2);
					flag = false;
					if (value4 == "X")
					{
						stringBuilder.Append("<color=#9A9A9A><s>").Append(StripColors(stringBuilder2.ToString())).Append("</s></color>");
						continue;
					}
					stringBuilder.Append(stringBuilder2);
					if (c2 == c)
					{
						flag2 = true;
					}
				}
				if (flag2)
				{
					Bullseye(stringBuilder, shape, ColorOf(c));
				}
			}
			return stringBuilder.ToString();
		}

		private (string sign, string color, char group) Op(string type)
		{
			switch (type)
			{
			case "A":
			case "3":
				return (sign: "+", color: _colAdd, group: 'A');
			case "S":
			case "1":
				return (sign: "-", color: _colSub, group: 'S');
			case "M":
				return (sign: _times, color: _colMul, group: 'M');
			case "D":
				return (sign: _divide, color: _colDiv, group: 'D');
			default:
				return (sign: "?", color: "#9A9A9A", group: '?');
			}
		}

		private string ColorOf(char group)
		{
			return group switch
			{
				'A' => _colAdd, 
				'S' => _colSub, 
				'M' => _colMul, 
				'D' => _colDiv, 
				_ => "#9A9A9A", 
			};
		}

		private string Number(int n, string mark)
		{
			switch (mark)
			{
			case "W":
				return $"({n}{_times}2)";
			case "H":
				return $"({n}/2)";
			case "V":
				return $"rev({n})";
			case null:
			case "X":
				return n.ToString();
			default:
				return $"{n}<sup>?</sup>";
			}
		}

		private static string StripColors(string s)
		{
			return Regex.Replace(s, "</?color[^>]*>", "");
		}

		private unsafe static void Bullseye(StringBuilder sb, string shape, string color)
		{
			if (!(shape == "E"))
			{
				if (!Shapes.TryGetValue(shape, out var value))
				{
					value = new string[1] { "?" };
				}
				string[] array = value;
				for (int i = 0; i < array.Length; i++)
				{
					object obj = array[i] switch
					{
						"sq" => ("(", ")<sup>2</sup>"), 
						"sq4" => ("(", ")<sup>4</sup>"), 
						"rev" => ("rev(", ")"), 
						"r1" => ("round(", ")"), 
						"r10" => ("round<sub>10</sub>(", ")"), 
						"r100" => ("round<sub>100</sub>(", ")"), 
						_ => ("(", ")<sup>?</sup>"), 
					};
					string item = ((ValueTuple<string, string>*)(&obj))->Item1;
					string item2 = ((ValueTuple<string, string>*)(&obj))->Item2;
					StringBuilder stringBuilder = sb.Insert(0, $"<color={color}>{item}</color>");
					StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(16, 2, stringBuilder);
					handler.AppendLiteral("<color=");
					handler.AppendFormatted(color);
					handler.AppendLiteral(">");
					handler.AppendFormatted(item2);
					handler.AppendLiteral("</color>");
					stringBuilder.Append(ref handler);
				}
			}
		}

		private bool MenuOpen()
		{
			if (_menuCheckBroken)
			{
				return false;
			}
			try
			{
				return UIScreen.AnyOpened;
			}
			catch (Exception ex)
			{
				_menuCheckBroken = true;
				Core.Log.Warning("[" + Name + "] menu detection failed, disabling it: " + ex.Message);
				return false;
			}
		}

		private void Sync(bool show)
		{
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_027e: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_tmp == (Object)null)
			{
				if ((Object)(object)_root != (Object)null)
				{
					Object.Destroy((Object)(object)_root);
					_root = null;
				}
				if (!show || _gaveUp || Time.unscaledTime < _nextTry)
				{
					return;
				}
				_nextTry = Time.unscaledTime + 1f;
				if (!TryCreate())
				{
					return;
				}
			}
			if (!show)
			{
				if (_root.activeSelf)
				{
					_root.SetActive(false);
				}
				return;
			}
			if (!_root.activeSelf)
			{
				_root.SetActive(true);
			}
			if (_shownText != _text)
			{
				((TMP_Text)_tmp).text = _text;
				_shownText = _text;
			}
			float num = Mathf.Max(10f, (float)Screen.height * Mathf.Clamp(_fontScale.Value, 0.01f, 0.2f));
			((TMP_Text)_tmp).fontSize = num;
			float num2 = num * 0.6f;
			if (_layoutText != _text || !Mathf.Approximately(_layoutSize, num))
			{
				_layoutText = _text;
				_layoutSize = num;
				Vector2 preferredValues = ((TMP_Text)_tmp).GetPreferredValues(_text, (float)Screen.width * 0.9f, 0f);
				float y = ((TMP_Text)_tmp).GetPreferredValues("+0", 0f, 0f).y;
				_layoutWidth = Mathf.Min(preferredValues.x, (float)Screen.width * 0.9f);
				_layoutLines = ((!(y > 0f)) ? 1 : Mathf.Max(1, Mathf.RoundToInt(preferredValues.y / y)));
			}
			float layoutWidth = _layoutWidth;
			float num3 = (float)_layoutLines * num * 0.95f + num * 0.3f;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 1f - Mathf.Clamp01(_posY.Value));
			_panel.anchorMin = val;
			_panel.anchorMax = val;
			_panel.pivot = new Vector2(0.5f, 0.5f);
			_panel.anchoredPosition = Vector2.zero;
			_panel.sizeDelta = new Vector2(layoutWidth + 2f * num2, num3);
			RectTransform rectTransform = ((TMP_Text)_tmp).rectTransform;
			rectTransform.anchorMin = Vector2.zero;
			rectTransform.anchorMax = Vector2.one;
			rectTransform.offsetMin = new Vector2(num2, 0f);
			rectTransform.offsetMax = new Vector2(0f - num2, 0f);
		}

		private bool TryCreate()
		{
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Expected O, but got Unknown
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Expected O, but got Unknown
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			string value = (_fontPref.Value ?? "").Trim();
			TMP_FontAsset val = null;
			TMP_FontAsset val2 = null;
			foreach (TMP_FontAsset item in Resources.