Decompiled source of TaleSpire Tilt Five v1.0.0

plugins/TaleSpireTiltFive/TaleSpireTiltFive.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DataModel;
using HarmonyLib;
using TiltFive;
using TiltFive.Logging;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("TiltFiveEditor")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace TaleSpireTiltFive
{
	internal class HiddenCreatureFilter : MonoBehaviour
	{
		internal Func<int> GmSeat = () => 1;

		internal const string HideVolumeBufferName = "TsHideVolumeCommands";

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

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

		private float nextRefresh;

		private int hiddenCreatureCount;

		private int volumeHiddenCount;

		private bool loggedActive;

		private bool loggedOverride;

		private int errors;

		private static readonly int ShowOverrideId = Shader.PropertyToID("G_ShowOverride");

		private bool overrideSaved;

		private float savedOverride;

		private readonly List<(Camera cam, CameraEvent evt, CommandBuffer cb)> detached = new List<(Camera, CameraEvent, CommandBuffer)>();

		private readonly Dictionary<Camera, List<(CameraEvent, CommandBuffer)>> boxBuffers = new Dictionary<Camera, List<(CameraEvent, CommandBuffer)>>();

		private static readonly CameraEvent[] AllEvents = (CameraEvent[])Enum.GetValues(typeof(CameraEvent));

		internal int OverridesApplied { get; private set; }

		private void OnEnable()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			Camera.onPreCull = (CameraCallback)Delegate.Combine((Delegate?)(object)Camera.onPreCull, (Delegate?)new CameraCallback(PreCull));
			Camera.onPostRender = (CameraCallback)Delegate.Combine((Delegate?)(object)Camera.onPostRender, (Delegate?)new CameraCallback(PostRender));
		}

		private void OnDisable()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			Camera.onPreCull = (CameraCallback)Delegate.Remove((Delegate?)(object)Camera.onPreCull, (Delegate?)new CameraCallback(PreCull));
			Camera.onPostRender = (CameraCallback)Delegate.Remove((Delegate?)(object)Camera.onPostRender, (Delegate?)new CameraCallback(PostRender));
			RestoreAll();
		}

		private void Update()
		{
			PlayerVisibility.GmSeat = GmSeat;
			if (Time.unscaledTime < nextRefresh)
			{
				return;
			}
			nextRefresh = Time.unscaledTime + 0.5f;
			try
			{
				hiddenRenderers.Clear();
				hiddenCreatureCount = 0;
				volumeHiddenCount = 0;
				CreatureBoardAsset[] array = Object.FindObjectsOfType<CreatureBoardAsset>();
				foreach (CreatureBoardAsset val in array)
				{
					bool flag = false;
					try
					{
						flag = val.IsExplicitlyHidden;
					}
					catch
					{
					}
					bool flag2 = !flag && PlayerVisibility.IsHiddenByVolume(val);
					if (flag || flag2)
					{
						if (flag)
						{
							hiddenCreatureCount++;
						}
						else
						{
							volumeHiddenCount++;
						}
						hiddenRenderers.AddRange(((Component)val).GetComponentsInChildren<Renderer>(false));
					}
				}
				if (hiddenCreatureCount + volumeHiddenCount > 0 && !loggedActive && SeatCameras.Count > 0)
				{
					loggedActive = true;
					TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Hidden filter active: {hiddenCreatureCount} GM-hidden + {volumeHiddenCount} hide-volume creature(s) ({hiddenRenderers.Count} renderers) kept out of non-GM glasses.");
				}
				RefreshBoxBuffers();
			}
			catch (Exception ex)
			{
				if (errors++ < 10)
				{
					TaleSpireTiltFivePlugin.Log.LogWarning((object)("Hidden filter refresh failed: " + ex.Message));
				}
			}
		}

		private void RefreshBoxBuffers()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			boxBuffers.Clear();
			foreach (Camera eyeCamera in TileRenderHooks.EyeCameras)
			{
				if ((Object)(object)eyeCamera == (Object)null)
				{
					continue;
				}
				List<(CameraEvent, CommandBuffer)> list = null;
				CameraEvent[] allEvents = AllEvents;
				foreach (CameraEvent val in allEvents)
				{
					CommandBuffer[] commandBuffers = eyeCamera.GetCommandBuffers(val);
					foreach (CommandBuffer val2 in commandBuffers)
					{
						if (val2 != null && val2.name == "TsHideVolumeCommands")
						{
							(list ?? (list = new List<(CameraEvent, CommandBuffer)>())).Add((val, val2));
						}
					}
				}
				if (list != null)
				{
					boxBuffers[eyeCamera] = list;
				}
			}
		}

		private void PreCull(Camera cam)
		{
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)cam == (Object)null)
			{
				return;
			}
			int num = SeatCameras.SeatOf(cam);
			if (num == 0 || num == GmSeat())
			{
				return;
			}
			RestoreAll();
			foreach (Renderer hiddenRenderer in hiddenRenderers)
			{
				if ((Object)(object)hiddenRenderer != (Object)null && hiddenRenderer.enabled)
				{
					hiddenRenderer.enabled = false;
					switchedOff.Add(hiddenRenderer);
				}
			}
			try
			{
				float globalFloat = Shader.GetGlobalFloat(ShowOverrideId);
				if (globalFloat != 0f)
				{
					savedOverride = globalFloat;
					overrideSaved = true;
					Shader.SetGlobalFloat(ShowOverrideId, 0f);
					OverridesApplied++;
					if (!loggedOverride)
					{
						loggedOverride = true;
						TaleSpireTiltFivePlugin.Log.LogInfo((object)"GM show-hidden override is on; keeping hide-volume contents out of non-GM glasses.");
					}
				}
			}
			catch (Exception ex)
			{
				if (errors++ < 10)
				{
					TaleSpireTiltFivePlugin.Log.LogWarning((object)("Show-override filter failed: " + ex.Message));
				}
			}
			if (!boxBuffers.TryGetValue(cam, out var value))
			{
				return;
			}
			foreach (var (val, val2) in value)
			{
				try
				{
					cam.RemoveCommandBuffer(val, val2);
					detached.Add((cam, val, val2));
				}
				catch
				{
				}
			}
		}

		private void PostRender(Camera cam)
		{
			if (SeatCameras.IsEye(cam))
			{
				RestoreAll();
			}
		}

		private void LateUpdate()
		{
			RestoreAll();
		}

		private void RestoreAll()
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < switchedOff.Count; i++)
			{
				if ((Object)(object)switchedOff[i] != (Object)null)
				{
					switchedOff[i].enabled = true;
				}
			}
			switchedOff.Clear();
			if (overrideSaved)
			{
				overrideSaved = false;
				try
				{
					Shader.SetGlobalFloat(ShowOverrideId, savedOverride);
				}
				catch
				{
				}
			}
			for (int j = 0; j < detached.Count; j++)
			{
				var (val, val2, val3) = detached[j];
				try
				{
					if ((Object)(object)val != (Object)null && val3 != null)
					{
						val.AddCommandBuffer(val2, val3);
					}
				}
				catch
				{
				}
			}
			detached.Clear();
		}

		internal string Describe()
		{
			return $"hidden={hiddenCreatureCount} inVolumes={volumeHiddenCount} overrides={OverridesApplied}";
		}
	}
	internal static class TileRenderHooks
	{
		internal static readonly List<Camera> EyeCameras = new List<Camera>();

		internal static Camera CullCamera;

		internal static Camera MainCamera;

		internal static bool WideCulling = true;

		internal static bool CullHookOk;

		internal static bool DrawHookOk;

		private static Harmony harmony;

		private static int drawCalls;

		private static bool reportedDraws;

		internal static bool Active => EyeCameras.Count > 0;

		internal static void Install()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			ManualLogSource log = TaleSpireTiltFivePlugin.Log;
			harmony = new Harmony("com.faaze.talespire.tiltfive");
			Type type = AccessTools.TypeByName("Unity.Rendering.FrustumPlanes");
			MethodInfo methodInfo = type?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "BuildSoaPlanePackets" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == typeof(Camera));
			if (methodInfo != null)
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TileRenderHooks), "BuildPlanesPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				CullHookOk = true;
				log.LogInfo((object)("Hooked " + type.FullName + ".BuildSoaPlanePackets(Camera, ...) for wide culling."));
			}
			else
			{
				log.LogWarning((object)"FrustumPlanes.BuildSoaPlanePackets(Camera, ...) not found; tiles outside TaleSpire's own view will be culled in the glasses.");
			}
			Type type2 = AccessTools.TypeByName("BoardPresentation");
			MethodInfo methodInfo2 = ((type2 != null) ? AccessTools.Method(type2, "CullAndRender", (Type[])null, (Type[])null) : null);
			if (methodInfo2 != null)
			{
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(TileRenderHooks), "CullAndRenderTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				log.LogInfo((object)"Hooked BoardPresentation.CullAndRender tile draws for the eye cameras.");
			}
			else
			{
				log.LogWarning((object)"BoardPresentation.CullAndRender not found; tiles won't render in the glasses.");
			}
		}

		private static void BuildPlanesPrefix(ref Camera cam)
		{
			if (WideCulling && Active && (Object)(object)CullCamera != (Object)null && (Object)(object)cam != (Object)null && (Object)(object)cam == (Object)(object)MainCamera)
			{
				cam = CullCamera;
			}
		}

		private static IEnumerable<CodeInstruction> CullAndRenderTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo replacement = AccessTools.Method(typeof(TileRenderHooks), "DrawForAllCameras", (Type[])null, (Type[])null);
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if ((instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && instruction.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(Graphics) && methodInfo.Name == "DrawMeshInstancedIndirect" && methodInfo.GetParameters().Length == 12)
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = replacement;
					replaced++;
				}
				yield return instruction;
			}
			DrawHookOk = replaced > 0;
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"CullAndRender transpiler replaced {replaced} tile draw call(s).");
			if (replaced == 0)
			{
				TaleSpireTiltFivePlugin.Log.LogWarning((object)"Tile draw hook found no draw calls to patch; TaleSpire may have changed. Tiles will be missing in the glasses.");
			}
		}

		public static void DrawForAllCameras(Mesh mesh, int submeshIndex, Material material, Bounds bounds, ComputeBuffer bufferWithArgs, int argsOffset, MaterialPropertyBlock properties, ShadowCastingMode castShadows, bool receiveShadows, int layer, Camera camera, LightProbeUsage lightProbeUsage)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			Graphics.DrawMeshInstancedIndirect(mesh, submeshIndex, material, bounds, bufferWithArgs, argsOffset, properties, castShadows, receiveShadows, layer, camera, lightProbeUsage);
			if ((Object)(object)camera == (Object)null || !Active)
			{
				return;
			}
			for (int i = 0; i < EyeCameras.Count; i++)
			{
				Camera val = EyeCameras[i];
				if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled)
				{
					Graphics.DrawMeshInstancedIndirect(mesh, submeshIndex, material, bounds, bufferWithArgs, argsOffset, properties, castShadows, receiveShadows, layer, val, lightProbeUsage);
					drawCalls++;
				}
			}
			if (!reportedDraws && drawCalls > 0)
			{
				reportedDraws = true;
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Tile draws are now being issued for {EyeCameras.Count} eye camera(s).");
			}
		}
	}
	internal static class TaleSpireOps
	{
		internal static bool CanEdit(out string why)
		{
			why = null;
			try
			{
				if (!InBoard())
				{
					why = "not in a board";
					return false;
				}
				if (!IsGm())
				{
					why = "TaleSpire isn't in GM mode";
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				why = ex.Message;
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool InBoard()
		{
			return BoardSessionManager.InBoard;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool IsGm()
		{
			return LocalClient.IsInGmMode;
		}

		internal static HashSet<string> CreatureIds()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>();
			CreatureBoardAsset[] array = Object.FindObjectsOfType<CreatureBoardAsset>();
			foreach (CreatureBoardAsset val in array)
			{
				try
				{
					hashSet.Add(((object)val.CreatureId/*cast due to .constrained prefix*/).ToString());
				}
				catch
				{
				}
			}
			return hashSet;
		}
	}
	internal static class SeatCameras
	{
		private static readonly Dictionary<Camera, int> eyeSeat = new Dictionary<Camera, int>();

		internal static int Count => eyeSeat.Count;

		internal static void Refresh(int playerCount)
		{
			eyeSeat.Clear();
			for (int i = 1; i <= Mathf.Clamp(playerCount, 1, 4); i++)
			{
				PlayerIndex playerIndex = (PlayerIndex)i;
				try
				{
					if (Player.IsConnected(playerIndex))
					{
						Camera leftEye = Glasses.GetLeftEye(playerIndex);
						Camera rightEye = Glasses.GetRightEye(playerIndex);
						if ((Object)(object)leftEye != (Object)null)
						{
							eyeSeat[leftEye] = i;
						}
						if ((Object)(object)rightEye != (Object)null)
						{
							eyeSeat[rightEye] = i;
						}
					}
				}
				catch
				{
				}
			}
		}

		internal static int SeatOf(Camera cam)
		{
			if (!((Object)(object)cam != (Object)null) || !eyeSeat.TryGetValue(cam, out var value))
			{
				return 0;
			}
			return value;
		}

		internal static bool IsEye(Camera cam)
		{
			if ((Object)(object)cam != (Object)null)
			{
				return eyeSeat.ContainsKey(cam);
			}
			return false;
		}
	}
	internal interface ITable
	{
		float SurfaceY { get; }

		Camera PcCamera { get; }

		bool FollowCamera { get; set; }

		string LevelText { get; }

		float LevelFloorY { get; }

		Transform BoardFor(int seat);

		bool IsPersonal(int seat);

		void SetPersonal(int seat, bool personal);

		float ZoomFor(int seat);

		void SetZoomFor(int seat, float zoom);

		void BoardMovedBy(int seat);

		float WorldPerCm(int seat);

		string StepLevel(int direction);

		string ClearLevel();

		void EveryoneToTable();

		string SetLevel(int storey);

		bool IsSeatConnected(int seat);
	}
	internal static class CutPlane
	{
		internal static bool Broken;

		internal static bool TryGet(out float height)
		{
			height = 0f;
			if (Broken)
			{
				return false;
			}
			try
			{
				height = GetInner();
				return true;
			}
			catch (Exception e)
			{
				Fail(e);
				return false;
			}
		}

		internal static bool TrySet(float height)
		{
			if (Broken)
			{
				return false;
			}
			try
			{
				SetInner(height);
				return true;
			}
			catch (Exception e)
			{
				Fail(e);
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static float GetInner()
		{
			return CameraController.HideHeightPlaneValue;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void SetInner(float h)
		{
			CameraController.SetHidePlaneHeight(h);
		}

		private static void Fail(Exception e)
		{
			Broken = true;
			TaleSpireTiltFivePlugin.Log.LogWarning((object)("TaleSpire cut plane not reachable (" + e.GetType().Name + ": " + e.Message + "); wand level control disabled."));
		}
	}
	[BepInPlugin("com.faaze.talespire.tiltfive", "TaleSpire Tilt Five", "1.0.0")]
	public class TaleSpireTiltFivePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.faaze.talespire.tiltfive";

		public const string PluginName = "TaleSpire Tilt Five";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static TaleSpireTiltFivePlugin Instance;

		internal ConfigEntry<float> TileSizeCm;

		internal ConfigEntry<float> TileWorldUnits;

		internal ConfigEntry<float> BoardZoom;

		internal ConfigEntry<float> BoardHeight;

		internal ConfigEntry<bool> FollowCamera;

		internal ConfigEntry<bool> FollowCameraYaw;

		internal ConfigEntry<bool> ShowPreview;

		internal ConfigEntry<bool> ShowOverlay;

		internal ConfigEntry<string> PerfMode;

		internal ConfigEntry<float> EyeShadowDistance;

		internal ConfigEntry<float> EyeLodBias;

		internal ConfigEntry<bool> EyePostProcessing;

		internal ConfigEntry<bool> CopyPostProcessing;

		internal ConfigEntry<bool> MirrorCommandBuffers;

		internal ConfigEntry<bool> MatchGlassesFramerate;

		internal ConfigEntry<bool> VerboseDiagnostics;

		internal ConfigEntry<bool> WideCulling;

		internal ConfigEntry<float> CullRadius;

		internal ConfigEntry<int> PlayerCount;

		internal ConfigEntry<bool> WandControls;

		internal ConfigEntry<bool> PointerFromEyes;

		internal ConfigEntry<int> GmSeat;

		internal ConfigEntry<bool> RestrictToOwnMinis;

		internal ConfigEntry<bool> HideHiddenFromPlayers;

		internal ConfigEntry<bool> AllowPersonalView;

		internal ConfigEntry<float> LevelStep;

		internal ConfigEntry<float> LevelCutHeight;

		private TaleSpireRig rig;

		private void Awake()
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Expected O, but got Unknown
			//IL_0435: Unknown result type (might be due to invalid IL or missing references)
			//IL_0460: Unknown result type (might be due to invalid IL or missing references)
			//IL_0465: Unknown result type (might be due to invalid IL or missing references)
			//IL_046d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0473: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			TileSizeCm = ((BaseUnityPlugin)this).Config.Bind<float>("Scale", "TileSizeCm", 2.54f, "Physical size of one TaleSpire tile on the Tilt Five board, in centimeters. 2.54 = classic 1-inch miniature grid.");
			TileWorldUnits = ((BaseUnityPlugin)this).Config.Bind<float>("Scale", "TileWorldUnits", 1f, "Size of one TaleSpire tile in Unity world units (TaleSpire tiles are believed to be 1 unit).");
			BoardZoom = ((BaseUnityPlugin)this).Config.Bind<float>("Scale", "BoardZoom", 1f, "Extra zoom multiplier. >1 shows more of the map on the board (smaller minis). Adjust live with PageUp/PageDown.");
			BoardHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "BoardHeight", -0.2f, "World Y height (in tiles) the physical gameboard surface sits at. Lower = scene sits higher above the board. Adjust live with Home/End.");
			FollowCamera = ((BaseUnityPlugin)this).Config.Bind<bool>("Board", "FollowCamera", true, "Keep the board centered on where the TaleSpire camera is looking. Toggle live with F9.");
			FollowCameraYaw = ((BaseUnityPlugin)this).Config.Bind<bool>("Board", "FollowCameraYaw", false, "Also rotate the board when the TaleSpire camera rotates. Off = use F10 to align once.");
			PerfMode = ((BaseUnityPlugin)this).Config.Bind<string>("Performance", "Mode", "Auto", new ConfigDescription("Eye-camera quality. Auto = full quality for 1-2 players, the Fast settings from 3 players up. Quality = always full. Fast = always the Fast settings.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Auto", "Quality", "Fast" }), Array.Empty<object>()));
			EyeShadowDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "EyeShadowDistance", 25f, "Fast mode: shadow distance for the glasses, in tiles (times board zoom).");
			EyeLodBias = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "EyeLodBias", 0.7f, "Fast mode: level-of-detail bias for the glasses (lower = simpler models sooner).");
			EyePostProcessing = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "EyePostProcessing", false, "Fast mode: keep post-processing (colour grading, bloom) on the glasses.");
			ShowOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOverlay", false, "Frame-time overlay in the corner of the PC screen (fps, per-eye render time, players, health). Toggle live with F3.");
			ShowPreview = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowPreview", true, "Draw a picture-in-picture mirror of the left eye in the corner of the TaleSpire window. Toggle live with F11.");
			CopyPostProcessing = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "CopyPostProcessing", true, "Clone TaleSpire's PostProcessLayer onto the eye cameras (color grading, bloom, fog).");
			MirrorCommandBuffers = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "MirrorCommandBuffers", true, "Copy TaleSpire's per-camera command buffers (its custom lights, decals, cut caps) onto the eye cameras.");
			WideCulling = ((BaseUnityPlugin)this).Config.Bind<bool>("Rendering", "WideCulling", true, "While the glasses are active, cull TaleSpire's tiles against a wide area around the board instead of only TaleSpire's own camera view.");
			CullRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Rendering", "CullRadius", 80f, "Radius (in tiles, multiplied by board zoom) around the board center that stays rendered while the glasses are active.");
			PlayerCount = ((BaseUnityPlugin)this).Config.Bind<int>("Players", "PlayerCount", 4, "How many Tilt Five glasses (players) to support at once, 1-4. Each extra player adds two eye renders.");
			WandControls = ((BaseUnityPlugin)this).Config.Bind<bool>("Players", "WandControls", true, "Let each player point with their wand and pick up / move minis (trigger = grab/drop, B = cancel).");
			PointerFromEyes = ((BaseUnityPlugin)this).Config.Bind<bool>("Players", "PointerFromEyes", true, "Wand selection ray goes from the player's eyes through the wand tip onto the board. Off = straight out of the wand tip.");
			GmSeat = ((BaseUnityPlugin)this).Config.Bind<int>("Players", "GmSeat", 1, "Which Tilt Five player (1-4) is the GM. The GM can move any mini, assign owners (A), move the board (hold 2 / hold 1 + stick) and sees GM-hidden creatures.");
			RestrictToOwnMinis = ((BaseUnityPlugin)this).Config.Bind<bool>("Players", "RestrictToOwnMinis", true, "Players can only pick up minis the GM has assigned to them. Off = anyone can move any visible mini.");
			HideHiddenFromPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("Players", "HideHiddenFromPlayers", true, "Keep GM-hidden creatures out of the non-GM players' glasses.");
			AllowPersonalView = ((BaseUnityPlugin)this).Config.Bind<bool>("Players", "AllowPersonalView", true, "Players can press Y to unlock their own board and move around the scene on their own (Y again = back to the table view).");
			LevelStep = ((BaseUnityPlugin)this).Config.Bind<float>("Levels", "LevelStep", 2.5f, "Height of one storey in tiles, for the GM's X / Y level buttons (2.5 = a floor tile plus a standard 2-high wall).");
			LevelCutHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Levels", "LevelCutHeight", 2.25f, "How far above the current level's floor TaleSpire's cut plane goes: everything higher (roof, upper storeys) is hidden.");
			MatchGlassesFramerate = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "MatchGlassesFramerate", true, "Let the Tilt Five SDK set the frame-rate cap to match the glasses.");
			VerboseDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseDiagnostics", true, "Log cameras, command buffers and TaleSpire camera-controller members to help with development.");
			LogFix.Apply();
			Installer.Run();
			try
			{
				TileRenderHooks.Install();
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to install TaleSpire render hooks: {arg}");
			}
			Log.LogInfo((object)string.Format("{0} {1} loading (Unity {2}, gfx {3}).", "TaleSpire Tilt Five", "1.0.0", Application.unityVersion, SystemInfo.graphicsDeviceType));
			Application.quitting += OnQuit;
			GameObject val = new GameObject("TaleSpireTiltFive Bootstrap")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<Bootstrapper>().Plugin = this;
		}

		private static void OnQuit()
		{
			try
			{
				if (!NativePlugin.DetectedMissingDLL)
				{
					NativePlugin.UnloadWorkaround();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("UnloadWorkaround failed: " + ex.Message));
				}
			}
		}

		private void OnDestroy()
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogInfo((object)"Plugin component destroyed (expected in some games; rig keeps running on its own object).");
			}
		}

		internal void CreateRig(Camera main)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			ManualLogSource log = Log;
			object[] obj = new object[5]
			{
				((Object)main).name,
				main.depth,
				main.renderingPath,
				main.allowHDR,
				null
			};
			Scene scene = ((Component)main).gameObject.scene;
			obj[4] = ((Scene)(ref scene)).name;
			log.LogInfo((object)string.Format("Found TaleSpire camera '{0}' (depth {1}, path {2}, hdr {3}, scene '{4}').", obj));
			if (VerboseDiagnostics.Value)
			{
				Diagnostics.LogCameras();
				Diagnostics.LogCameraControllerMembers();
			}
			GameObject val = new GameObject("TaleSpireTiltFive Rig");
			((Object)val).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)val);
			rig = val.AddComponent<TaleSpireRig>();
			rig.Initialize(this, main);
		}
	}
	internal class Bootstrapper : MonoBehaviour
	{
		internal TaleSpireTiltFivePlugin Plugin;

		private float nextCheck;

		private float nextReport;

		private bool done;

		private void Update()
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (done || Plugin == null || Time.unscaledTime < nextCheck)
			{
				return;
			}
			nextCheck = Time.unscaledTime + 1f;
			Camera val = CameraUtil.FindTaleSpireCamera();
			if ((Object)(object)val != (Object)null && CameraUtil.LooksLikeGameplayCamera(val))
			{
				done = true;
				try
				{
					Plugin.CreateRig(val);
					return;
				}
				catch (Exception arg)
				{
					TaleSpireTiltFivePlugin.Log.LogError((object)$"CreateRig failed: {arg}");
					return;
				}
			}
			if (Time.unscaledTime >= nextReport)
			{
				nextReport = Time.unscaledTime + 15f;
				Camera[] allCameras = Camera.allCameras;
				ManualLogSource log = TaleSpireTiltFivePlugin.Log;
				Scene activeScene = SceneManager.GetActiveScene();
				log.LogInfo((object)($"Waiting for a board camera... scene '{((Scene)(ref activeScene)).name}', {allCameras.Length} active camera(s): " + string.Join(", ", allCameras.Select((Camera c) => $"'{((Object)c).name}'(tag {((Component)c).tag}, rt {(Object)(object)c.targetTexture != (Object)null})"))));
			}
		}
	}
	internal static class CameraUtil
	{
		internal static readonly string[] OurCameraNames = new string[4] { "Left Eye Camera", "Right Eye Camera", "T5 Spectator", "T5 Eye Template" };

		internal static bool IsOurs(Camera c)
		{
			if ((Object)(object)c != (Object)null)
			{
				return OurCameraNames.Contains(((Object)((Component)c).gameObject).name);
			}
			return false;
		}

		internal static bool LooksLikeGameplayCamera(Camera c)
		{
			if ((Object)(object)c != (Object)null && !IsOurs(c) && !c.orthographic)
			{
				return ((Behaviour)c).isActiveAndEnabled;
			}
			return false;
		}

		internal static Camera FindTaleSpireCamera()
		{
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null && ((Behaviour)main).isActiveAndEnabled && !IsOurs(main))
			{
				return main;
			}
			Camera val = null;
			Camera[] allCameras = Camera.allCameras;
			foreach (Camera val2 in allCameras)
			{
				if (!IsOurs(val2) && !((Object)(object)val2.targetTexture != (Object)null) && ((Object)(object)val == (Object)null || val2.depth > val.depth))
				{
					val = val2;
				}
			}
			return val;
		}

		internal static void ApplyLook(Camera eye, Camera source)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			eye.clearFlags = source.clearFlags;
			eye.backgroundColor = source.backgroundColor;
			eye.renderingPath = source.renderingPath;
			eye.allowHDR = source.allowHDR;
			eye.allowMSAA = source.allowMSAA;
			eye.depthTextureMode = source.depthTextureMode;
			eye.useOcclusionCulling = false;
			eye.cullingMask = source.cullingMask;
			eye.layerCullDistances = source.layerCullDistances;
			eye.layerCullSpherical = source.layerCullSpherical;
			eye.opaqueSortMode = source.opaqueSortMode;
			eye.transparencySortMode = source.transparencySortMode;
			eye.stereoTargetEye = (StereoTargetEyeMask)0;
		}
	}
	[DefaultExecutionOrder(-200)]
	internal class TaleSpireRig : MonoBehaviour, ITable, IRigControl
	{
		private TaleSpireTiltFivePlugin plugin;

		private Camera taleSpireCamera;

		private GameObject managerObject;

		private TiltFiveManager2 manager;

		private GameBoard gameBoard;

		private Camera spectatorCamera;

		private Camera eyeTemplate;

		private Camera cullCamera;

		private readonly HashSet<Camera> configuredEyes = new HashSet<Camera>();

		private readonly Dictionary<Camera, List<KeyValuePair<CameraEvent, CommandBuffer>>> mirroredBuffers = new Dictionary<Camera, List<KeyValuePair<CameraEvent, CommandBuffer>>>();

		private float nextEyeScan;

		private readonly bool[] wasConnected = new bool[4];

		private int mirroredSourceCount = -1;

		private WandInteraction wands;

		private HiddenCreatureFilter hiddenFilter;

		private SessionGuard guard;

		private PerfTuning perf;

		private SessionFeatures session;

		private readonly GameBoard[] personalBoards = new GameBoard[4];

		private readonly bool[] personal = new bool[4];

		private bool levelOn;

		private float levelY;

		private float savedCut;

		private float frameAccum;

		private float worstFrame;

		private int frameCount;

		private Vector3 lastFocus;

		private static readonly Rect PreviewRect = new Rect(0.68f, 0.02f, 0.3f, 0.3f);

		private float nextStatus;

		private float nextSeatScan;

		private int errorCount;

		private float lastCullRadius;

		private static readonly Vector2[] ViewportSamples = (Vector2[])(object)new Vector2[9]
		{
			new Vector2(0f, 0f),
			new Vector2(1f, 0f),
			new Vector2(0f, 1f),
			new Vector2(1f, 1f),
			new Vector2(0.5f, 0f),
			new Vector2(0.5f, 1f),
			new Vector2(0f, 0.5f),
			new Vector2(1f, 0.5f),
			new Vector2(0.5f, 0.5f)
		};

		private static readonly string[] SkipBufferPrefixes = new string[6] { "Post-processing", "Opaque Only Post-processing", "Deferred Ambient Occlusion", "Deferred Fog", "Glasses HDR", "PixelPicker" };

		private int PlayerCountClamped => Mathf.Clamp(plugin.PlayerCount.Value, 1, 4);

		private float ContentScaleRatio => Mathf.Max(0.01f, plugin.TileSizeCm.Value / Mathf.Max(0.0001f, plugin.TileWorldUnits.Value));

		public Camera PcCamera => taleSpireCamera;

		public float SurfaceY => plugin.BoardHeight.Value + (levelOn ? levelY : 0f);

		public bool FollowCamera
		{
			get
			{
				return plugin.FollowCamera.Value;
			}
			set
			{
				if (plugin.FollowCamera.Value != value)
				{
					plugin.FollowCamera.Value = value;
					TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Follow camera: {value}");
				}
			}
		}

		public string LevelText
		{
			get
			{
				if (!levelOn)
				{
					return "All levels";
				}
				float num = Mathf.Max(0.25f, plugin.LevelStep.Value);
				int num2 = Mathf.RoundToInt(levelY / num);
				if (num2 != 0)
				{
					if (num2 <= 0)
					{
						return $"Level {-num2} down";
					}
					return $"Level {num2} up";
				}
				return "Ground level";
			}
		}

		public float LevelFloorY
		{
			get
			{
				if (!levelOn)
				{
					return 0f;
				}
				return levelY;
			}
		}

		public int EyeCameraCount => TileRenderHooks.EyeCameras.Count((Camera c) => (Object)(object)c != (Object)null);

		internal void Initialize(TaleSpireTiltFivePlugin owner, Camera mainCamera)
		{
			plugin = owner;
			taleSpireCamera = mainCamera;
			try
			{
				BuildRig();
				if (plugin.WandControls.Value)
				{
					wands = ((Component)this).gameObject.AddComponent<WandInteraction>();
					wands.PlayerCount = PlayerCountClamped;
					wands.BoardHeight = () => SurfaceY;
					wands.WorldPerTile = () => plugin.TileWorldUnits.Value;
					wands.HeadThroughTip = () => plugin.PointerFromEyes.Value;
					wands.RestrictToOwned = () => plugin.RestrictToOwnMinis.Value;
					wands.GmSeat = () => Mathf.Clamp(plugin.GmSeat.Value, 1, 4);
					wands.Table = this;
					wands.AllowPersonalView = () => plugin.AllowPersonalView.Value;
					wands.OwnersFile = Path.Combine(Paths.ConfigPath, "com.faaze.talespire.tiltfive.owners.txt");
					wands.Init();
				}
				perf = ((Component)this).gameObject.AddComponent<PerfTuning>();
				perf.Plugin = plugin;
				perf.PlayerCount = PlayerCountClamped;
				session = ((Component)this).gameObject.AddComponent<SessionFeatures>();
				session.Wands = wands;
				session.GmSeat = () => Mathf.Clamp(plugin.GmSeat.Value, 1, 4);
				session.WorldPerTile = () => plugin.TileWorldUnits.Value;
				session.StartPointsFile = Path.Combine(Paths.ConfigPath, "com.faaze.talespire.tiltfive.startpoints.txt");
				session.Init();
				if ((Object)(object)wands != (Object)null)
				{
					wands.Session = session;
				}
				guard = ((Component)this).gameObject.AddComponent<SessionGuard>();
				guard.Plugin = plugin;
				guard.Rig = this;
				guard.Wands = wands;
				guard.PlayerCount = PlayerCountClamped;
				if (plugin.HideHiddenFromPlayers.Value)
				{
					hiddenFilter = ((Component)this).gameObject.AddComponent<HiddenCreatureFilter>();
					hiddenFilter.GmSeat = () => Mathf.Clamp(plugin.GmSeat.Value, 1, 4);
				}
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Tilt Five rig created for up to {PlayerCountClamped} player(s). Hotkeys: F6 everyone back to the table view, F7 cycle preview player, F8 recenter, F9 follow on/off, F10 align yaw, PageUp/PageDown zoom, Home/End board height, F11 preview.");
			}
			catch (Exception arg)
			{
				TaleSpireTiltFivePlugin.Log.LogError((object)$"Failed to build Tilt Five rig: {arg}");
				((Behaviour)this).enabled = false;
			}
		}

		private void BuildRig()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Expected O, but got Unknown
			//IL_0286: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("T5 GameBoard");
			val.transform.SetParent(((Component)this).transform, false);
			gameBoard = val.AddComponent<GameBoard>();
			lastFocus = (Vector3)(((??)ComputeFocusPoint(taleSpireCamera, out var _)) ?? Vector3.zero);
			val.transform.position = lastFocus;
			val.transform.rotation = Quaternion.Euler(0f, ((Component)taleSpireCamera).transform.eulerAngles.y, 0f);
			gameBoard.localScale = Mathf.Max(0.01f, plugin.BoardZoom.Value);
			GameObject val2 = new GameObject("T5 Eye Template");
			val2.SetActive(false);
			val2.transform.SetParent(((Component)this).transform, false);
			eyeTemplate = val2.AddComponent<Camera>();
			((Behaviour)eyeTemplate).enabled = true;
			CameraUtil.ApplyLook(eyeTemplate, taleSpireCamera);
			if (plugin.CopyPostProcessing.Value)
			{
				PostProcessCloner.TryClone(taleSpireCamera, val2);
			}
			GameObject val3 = new GameObject("T5 Cull Camera");
			val3.transform.SetParent(((Component)this).transform, false);
			cullCamera = val3.AddComponent<Camera>();
			((Behaviour)cullCamera).enabled = false;
			cullCamera.aspect = 1f;
			TileRenderHooks.CullCamera = cullCamera;
			TileRenderHooks.MainCamera = taleSpireCamera;
			UpdateCullCamera();
			GameObject val4 = new GameObject("T5 Spectator");
			val4.transform.SetParent(((Component)this).transform, false);
			spectatorCamera = val4.AddComponent<Camera>();
			spectatorCamera.clearFlags = (CameraClearFlags)2;
			spectatorCamera.backgroundColor = Color.black;
			spectatorCamera.cullingMask = 0;
			spectatorCamera.allowHDR = false;
			spectatorCamera.allowMSAA = false;
			ApplyPreviewMode();
			managerObject = new GameObject("T5 Manager");
			managerObject.SetActive(false);
			managerObject.transform.SetParent(((Component)this).transform, false);
			manager = managerObject.AddComponent<TiltFiveManager2>();
			manager.supportedPlayerCount = (uint)PlayerCountClamped;
			manager.graphicsSettings.matchGlassesFramerate = plugin.MatchGlassesFramerate.Value;
			for (int i = 0; i < 4; i++)
			{
				GameObject val5 = new GameObject($"T5 GameBoard P{i + 1} (personal)");
				val5.transform.SetParent(((Component)this).transform, false);
				personalBoards[i] = val5.AddComponent<GameBoard>();
				personalBoards[i].localScale = gameBoard.localScale;
				if (manager.allPlayerSettings[i] == null)
				{
					manager.allPlayerSettings[i] = new PlayerSettings();
				}
				PlayerSettings obj = manager.allPlayerSettings[i];
				obj.PlayerIndex = (PlayerIndex)(i + 1);
				obj.gameboardSettings.currentGameBoard = gameBoard;
				obj.scaleSettings.contentScaleUnit = LengthUnit.Centimeters;
				obj.scaleSettings.contentScaleRatio = ContentScaleRatio;
				obj.glassesSettings.cameraTemplate = eyeTemplate;
				obj.glassesSettings.cloneCameraTemplateChildren = false;
				obj.glassesSettings.cullingMask = LayerMask.op_Implicit(taleSpireCamera.cullingMask);
				obj.glassesSettings.friendlyName = "TaleSpire";
			}
			SpectatorSettings spectatorSettings = manager.spectatorSettings;
			spectatorSettings.spectatorCamera = spectatorCamera;
			spectatorSettings.spectatedPlayer = PlayerIndex.One;
			spectatorSettings.glassesMirrorMode = GlassesMirrorMode.LeftEye;
			spectatorSettings.cullingMask = LayerMask.op_Implicit(0);
			spectatorSettings.rect = PreviewRect;
			managerObject.SetActive(true);
		}

		private bool ValidSeat(int seat)
		{
			if (seat >= 1)
			{
				return seat <= 4;
			}
			return false;
		}

		public bool IsPersonal(int seat)
		{
			if (ValidSeat(seat))
			{
				return personal[seat - 1];
			}
			return false;
		}

		public Transform BoardFor(int seat)
		{
			if (!IsPersonal(seat))
			{
				return ((Component)gameBoard).transform;
			}
			return ((Component)personalBoards[seat - 1]).transform;
		}

		public float ZoomFor(int seat)
		{
			if (!IsPersonal(seat))
			{
				return plugin.BoardZoom.Value;
			}
			return personalBoards[seat - 1].localScale;
		}

		public void SetZoomFor(int seat, float zoom)
		{
			if (IsPersonal(seat))
			{
				personalBoards[seat - 1].localScale = Mathf.Clamp(zoom, 0.05f, 50f);
			}
			else
			{
				SetZoom(zoom, quiet: true);
			}
		}

		public float WorldPerCm(int seat)
		{
			return ZoomFor(seat) / ContentScaleRatio;
		}

		public void SetPersonal(int seat, bool on)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (ValidSeat(seat) && personal[seat - 1] != on)
			{
				GameBoard gameBoard = personalBoards[seat - 1];
				if (on)
				{
					((Component)gameBoard).transform.SetPositionAndRotation(((Component)this.gameBoard).transform.position, ((Component)this.gameBoard).transform.rotation);
					gameBoard.localScale = this.gameBoard.localScale;
				}
				personal[seat - 1] = on;
				PlayerSettings playerSettings = manager.allPlayerSettings[seat - 1];
				if (playerSettings != null)
				{
					playerSettings.gameboardSettings.currentGameBoard = (on ? gameBoard : this.gameBoard);
				}
				TaleSpireTiltFivePlugin.Log.LogInfo((object)(on ? $"Player {seat} unlocked a personal view." : $"Player {seat} is back on the table view."));
			}
		}

		public void BoardMovedBy(int seat)
		{
			if (!IsPersonal(seat) && plugin.FollowCamera.Value)
			{
				plugin.FollowCamera.Value = false;
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Player {seat} moved the table board with the wand; follow-camera turned off (F9 or F8 to bring it back).");
			}
		}

		public string StepLevel(int direction)
		{
			if (!CutPlane.TryGet(out var height))
			{
				return "Levels unavailable";
			}
			float num = Mathf.Max(0.25f, plugin.LevelStep.Value);
			if (!levelOn)
			{
				levelOn = true;
				savedCut = height;
				levelY = 0f;
			}
			else
			{
				levelY += (float)direction * num;
				levelY = Mathf.Clamp(levelY, -20f * num, 40f * num);
			}
			CutPlane.TrySet(levelY + plugin.LevelCutHeight.Value);
			string levelText = LevelText;
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"{levelText}: floor y {levelY:0.##}, cut plane {levelY + plugin.LevelCutHeight.Value:0.##}.");
			return levelText;
		}

		public void EveryoneToTable()
		{
			for (int i = 1; i <= 4; i++)
			{
				SetPersonal(i, on: false);
			}
			TaleSpireTiltFivePlugin.Log.LogInfo((object)"Everyone is back on the table view.");
		}

		public string SetLevel(int storey)
		{
			if (!CutPlane.TryGet(out var height))
			{
				return "Levels unavailable";
			}
			float num = Mathf.Max(0.25f, plugin.LevelStep.Value);
			if (!levelOn)
			{
				levelOn = true;
				savedCut = height;
			}
			levelY = (float)Mathf.Clamp(storey, -20, 40) * num;
			CutPlane.TrySet(levelY + plugin.LevelCutHeight.Value);
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"{LevelText}: floor y {levelY:0.##}, cut plane {levelY + plugin.LevelCutHeight.Value:0.##}.");
			return LevelText;
		}

		public bool IsSeatConnected(int seat)
		{
			try
			{
				return ValidSeat(seat) && Player.IsConnected((PlayerIndex)seat);
			}
			catch
			{
				return false;
			}
		}

		public string ClearLevel()
		{
			if (!levelOn)
			{
				return "All levels";
			}
			levelOn = false;
			levelY = 0f;
			CutPlane.TrySet(savedCut);
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Level mode off: cut plane back to {savedCut:0.##}.");
			return "All levels";
		}

		private void ApplyPreviewMode()
		{
			if (!((Object)(object)spectatorCamera == (Object)null) && !((Object)(object)taleSpireCamera == (Object)null))
			{
				spectatorCamera.depth = (plugin.ShowPreview.Value ? (taleSpireCamera.depth + 50f) : (taleSpireCamera.depth - 50f));
			}
		}

		private void Update()
		{
			try
			{
				UpdateInner();
			}
			catch (Exception arg)
			{
				if (errorCount++ < 20)
				{
					TaleSpireTiltFivePlugin.Log.LogError((object)$"Rig update error: {arg}");
				}
			}
		}

		private void UpdateInner()
		{
			if ((Object)(object)manager == (Object)null)
			{
				return;
			}
			if ((Object)(object)taleSpireCamera == (Object)null || !((Behaviour)taleSpireCamera).isActiveAndEnabled)
			{
				Camera val = CameraUtil.FindTaleSpireCamera();
				if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)taleSpireCamera)
				{
					taleSpireCamera = val;
					TaleSpireTiltFivePlugin.Log.LogInfo((object)("TaleSpire camera changed to '" + ((Object)val).name + "'."));
					configuredEyes.Clear();
					RetargetToCamera();
					ApplyPreviewMode();
				}
				if ((Object)(object)taleSpireCamera == (Object)null)
				{
					return;
				}
			}
			bool flag = false;
			for (int i = 0; i < PlayerCountClamped; i++)
			{
				if (flag)
				{
					break;
				}
				try
				{
					flag = Player.IsConnected((PlayerIndex)(i + 1));
				}
				catch
				{
				}
			}
			if ((Object)(object)spectatorCamera != (Object)null && ((Behaviour)spectatorCamera).enabled != flag)
			{
				((Behaviour)spectatorCamera).enabled = flag;
			}
			frameAccum += Time.unscaledDeltaTime;
			frameCount++;
			worstFrame = Mathf.Max(worstFrame, Time.unscaledDeltaTime);
			HandleHotkeys();
			UpdateBoard();
			TileRenderHooks.MainCamera = taleSpireCamera;
			TileRenderHooks.WideCulling = plugin.WideCulling.Value;
			UpdateCullCamera();
			if (Time.unscaledTime >= nextSeatScan)
			{
				nextSeatScan = Time.unscaledTime + 0.5f;
				SeatCameras.Refresh(PlayerCountClamped);
			}
			if (Time.unscaledTime >= nextEyeScan)
			{
				nextEyeScan = Time.unscaledTime + 1f;
				ScanEyeCameras();
				ReportConnection();
				LogFix.Apply();
			}
			if (Time.unscaledTime >= nextStatus)
			{
				nextStatus = Time.unscaledTime + 10f;
				Diagnostics.LogT5Status(gameBoard, taleSpireCamera, spectatorCamera);
				float num = ((frameCount > 0) ? (frameAccum / (float)frameCount) : 0f);
				TaleSpireTiltFivePlugin.Log.LogInfo((object)($"[perf] avg {num * 1000f:0.0} ms ({((num > 0f) ? (1f / num) : 0f):0} fps), worst {worstFrame * 1000f:0.0} ms, eye cameras {TileRenderHooks.EyeCameras.Count}, " + string.Format("hooks cull={0} draw={1}, cull radius {2:0} u, level {3}, personal [{4}]", TileRenderHooks.CullHookOk, TileRenderHooks.DrawHookOk, lastCullRadius, levelOn ? levelY.ToString("0.##") : "off", string.Join("", personal.Select((bool x) => (!x) ? "-" : "P"))) + (((Object)(object)wands != (Object)null) ? (" | " + wands.Describe()) : "") + (((Object)(object)hiddenFilter != (Object)null) ? (" " + hiddenFilter.Describe()) : "")));
				frameAccum = 0f;
				frameCount = 0;
				worstFrame = 0f;
			}
		}

		private void UpdateCullCamera()
		{
			//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)
			//IL_0066: 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_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0311: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0323: Unknown result type (might be due to invalid IL or missing references)
			//IL_0329: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: 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_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: 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_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: 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_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0349: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Unknown result type (might be due to invalid IL or missing references)
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)cullCamera == (Object)null || (Object)(object)gameBoard == (Object)null)
			{
				return;
			}
			Vector3 position = ((Component)gameBoard).transform.position;
			float num = Mathf.Max(5f, plugin.CullRadius.Value * plugin.TileWorldUnits.Value * gameBoard.localScale);
			float num2 = position.x - num;
			float num3 = position.x + num;
			float num4 = position.z - num;
			float num5 = position.z + num;
			List<Vector3> list = new List<Vector3>(ViewportSamples.Length + 16);
			for (int i = 0; i < 4; i++)
			{
				if (personal[i])
				{
					Vector3 position2 = ((Component)personalBoards[i]).transform.position;
					float num6 = Mathf.Max(5f, plugin.CullRadius.Value * plugin.TileWorldUnits.Value * personalBoards[i].localScale);
					Vector3[] array = (Vector3[])(object)new Vector3[4]
					{
						new Vector3(num6, 0f, 0f),
						new Vector3(0f - num6, 0f, 0f),
						new Vector3(0f, 0f, num6),
						new Vector3(0f, 0f, 0f - num6)
					};
					foreach (Vector3 val in array)
					{
						Vector3 val2 = position2 + val;
						list.Add(val2);
						num2 = Mathf.Min(num2, val2.x);
						num3 = Mathf.Max(num3, val2.x);
						num4 = Mathf.Min(num4, val2.z);
						num5 = Mathf.Max(num5, val2.z);
					}
				}
			}
			if ((Object)(object)taleSpireCamera != (Object)null)
			{
				Plane val3 = default(Plane);
				((Plane)(ref val3))..ctor(Vector3.up, new Vector3(0f, SurfaceY, 0f));
				Vector2[] viewportSamples = ViewportSamples;
				float num7 = default(float);
				foreach (Vector2 val4 in viewportSamples)
				{
					Ray val5 = taleSpireCamera.ViewportPointToRay(new Vector3(val4.x, val4.y, 0f));
					Vector3 val6 = ((((Plane)(ref val3)).Raycast(val5, ref num7) && num7 > 0f && num7 < 300f) ? ((Ray)(ref val5)).GetPoint(num7) : ((Ray)(ref val5)).GetPoint(300f));
					list.Add(val6);
					num2 = Mathf.Min(num2, val6.x);
					num3 = Mathf.Max(num3, val6.x);
					num4 = Mathf.Min(num4, val6.z);
					num5 = Mathf.Max(num5, val6.z);
				}
			}
			Vector3 val7 = default(Vector3);
			((Vector3)(ref val7))..ctor((num2 + num3) * 0.5f, position.y, (num4 + num5) * 0.5f);
			float num8 = Vector2.Distance(new Vector2(val7.x, val7.z), new Vector2(position.x, position.z)) + num;
			foreach (Vector3 item in list)
			{
				num8 = Mathf.Max(num8, Vector2.Distance(new Vector2(val7.x, val7.z), new Vector2(item.x, item.z)));
			}
			num8 = (lastCullRadius = Mathf.Min(num8 + 2f, 600f));
			((Component)cullCamera).transform.position = val7 + Vector3.up * 500f;
			((Component)cullCamera).transform.rotation = Quaternion.LookRotation(Vector3.down, Vector3.forward);
			cullCamera.fieldOfView = 2f * Mathf.Atan(num8 / 500f) * 57.29578f;
			cullCamera.nearClipPlane = 1f;
			cullCamera.farClipPlane = 1000f;
		}

		private void RetargetToCamera()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			CameraUtil.ApplyLook(eyeTemplate, taleSpireCamera);
			PlayerSettings[] allPlayerSettings = manager.allPlayerSettings;
			foreach (PlayerSettings playerSettings in allPlayerSettings)
			{
				if (playerSettings != null)
				{
					playerSettings.glassesSettings.cullingMask = LayerMask.op_Implicit(taleSpireCamera.cullingMask);
				}
			}
			PostProcessCloner.Retarget(((Component)eyeTemplate).gameObject, taleSpireCamera);
			Camera[] array = Resources.FindObjectsOfTypeAll<Camera>();
			foreach (Camera val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string name = ((Object)((Component)val).gameObject).name;
					if (name == "Left Eye Camera" || name == "Right Eye Camera")
					{
						PostProcessCloner.Retarget(((Component)val).gameObject, taleSpireCamera);
					}
				}
			}
		}

		private void HandleHotkeys()
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			if (Keys.Pressed((KeyCode)288))
			{
				CycleSpectator();
			}
			if (Keys.Pressed((KeyCode)287))
			{
				EveryoneToTable();
			}
			if (Keys.Pressed((KeyCode)289))
			{
				float distance;
				Vector3? val = ComputeFocusPoint(taleSpireCamera, out distance);
				if (val.HasValue)
				{
					lastFocus = val.Value;
					((Component)gameBoard).transform.position = lastFocus;
				}
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Board recentered at {lastFocus}.");
			}
			if (Keys.Pressed((KeyCode)290))
			{
				plugin.FollowCamera.Value = !plugin.FollowCamera.Value;
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Follow camera: {plugin.FollowCamera.Value}");
			}
			if (Keys.Pressed((KeyCode)291))
			{
				((Component)gameBoard).transform.rotation = Quaternion.Euler(0f, ((Component)taleSpireCamera).transform.eulerAngles.y, 0f);
				TaleSpireTiltFivePlugin.Log.LogInfo((object)"Board yaw aligned to camera.");
			}
			if (Keys.Pressed((KeyCode)278))
			{
				SetBoardHeight(plugin.BoardHeight.Value + 0.05f);
			}
			if (Keys.Pressed((KeyCode)279))
			{
				SetBoardHeight(plugin.BoardHeight.Value - 0.05f);
			}
			if (Keys.Pressed((KeyCode)280))
			{
				SetZoom(plugin.BoardZoom.Value * 1.25f);
			}
			if (Keys.Pressed((KeyCode)281))
			{
				SetZoom(plugin.BoardZoom.Value / 1.25f);
			}
			if (Keys.Pressed((KeyCode)292))
			{
				plugin.ShowPreview.Value = !plugin.ShowPreview.Value;
				ApplyPreviewMode();
			}
		}

		private void CycleSpectator()
		{
			SpectatorSettings spectatorSettings = manager.spectatorSettings;
			int spectatedPlayer = (int)spectatorSettings.spectatedPlayer;
			for (int i = 1; i <= PlayerCountClamped; i++)
			{
				int num = (spectatedPlayer - 1 + i) % PlayerCountClamped + 1;
				if (Player.IsConnected((PlayerIndex)num))
				{
					spectatorSettings.spectatedPlayer = (PlayerIndex)num;
					TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Preview now shows player {num}.");
					return;
				}
			}
			TaleSpireTiltFivePlugin.Log.LogInfo((object)"No other connected player to preview.");
		}

		private void SetBoardHeight(float h)
		{
			plugin.BoardHeight.Value = Mathf.Round(Mathf.Clamp(h, -50f, 50f) * 100f) / 100f;
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Board height {plugin.BoardHeight.Value:0.00}");
		}

		public void ApplyScale()
		{
			PlayerSettings[] allPlayerSettings = manager.allPlayerSettings;
			foreach (PlayerSettings playerSettings in allPlayerSettings)
			{
				if (playerSettings != null)
				{
					playerSettings.scaleSettings.contentScaleRatio = ContentScaleRatio;
				}
			}
		}

		public void SetZoomLive(float zoom)
		{
			SetZoom(zoom, quiet: true);
		}

		public void ApplyPreview()
		{
			ApplyPreviewMode();
		}

		public void OnBoardChanged(string why)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			levelOn = false;
			levelY = 0f;
			for (int i = 1; i <= 4; i++)
			{
				SetPersonal(i, on: false);
			}
			wands?.ResetForBoard();
			if ((Object)(object)taleSpireCamera == (Object)null || !((Behaviour)taleSpireCamera).isActiveAndEnabled)
			{
				taleSpireCamera = CameraUtil.FindTaleSpireCamera() ?? taleSpireCamera;
			}
			float distance;
			Vector3? val = ComputeFocusPoint(taleSpireCamera, out distance);
			if (val.HasValue)
			{
				lastFocus = val.Value;
				((Component)gameBoard).transform.position = lastFocus;
			}
			mirroredSourceCount = -1;
			TaleSpireTiltFivePlugin.Log.LogInfo((object)("[session] " + why + ": held minis released, menus closed, levels and personal views reset, table board recentred."));
		}

		private void SetZoom(float zoom, bool quiet = false)
		{
			zoom = Mathf.Clamp(zoom, 0.05f, 50f);
			plugin.BoardZoom.Value = zoom;
			gameBoard.localScale = zoom;
			if (!quiet)
			{
				TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Board zoom {zoom:0.###}");
			}
		}

		private void UpdateBoard()
		{
			//IL_005f: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			if (plugin.FollowCamera.Value)
			{
				float distance;
				Vector3? val = ComputeFocusPoint(taleSpireCamera, out distance);
				if (val.HasValue)
				{
					lastFocus = val.Value;
					((Component)gameBoard).transform.position = lastFocus;
				}
			}
			float surfaceY = SurfaceY;
			Vector3 position = ((Component)gameBoard).transform.position;
			if (!Mathf.Approximately(position.y, surfaceY))
			{
				((Component)gameBoard).transform.position = new Vector3(position.x, surfaceY, position.z);
			}
			for (int i = 0; i < 4; i++)
			{
				if (personal[i])
				{
					Transform transform = ((Component)personalBoards[i]).transform;
					if (!Mathf.Approximately(transform.position.y, surfaceY))
					{
						transform.position = new Vector3(transform.position.x, surfaceY, transform.position.z);
					}
				}
			}
			if (plugin.FollowCameraYaw.Value)
			{
				((Component)gameBoard).transform.rotation = Quaternion.Euler(0f, ((Component)taleSpireCamera).transform.eulerAngles.y, 0f);
			}
		}

		private Vector3? ComputeFocusPoint(Camera cam, out float distance)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			distance = 0f;
			if ((Object)(object)cam == (Object)null)
			{
				return null;
			}
			Plane val = default(Plane);
			((Plane)(ref val))..ctor(Vector3.up, new Vector3(0f, SurfaceY, 0f));
			Ray val2 = default(Ray);
			((Ray)(ref val2))..ctor(((Component)cam).transform.position, ((Component)cam).transform.forward);
			if (((Plane)(ref val)).Raycast(val2, ref distance) && distance > 0f && distance < 5000f)
			{
				return ((Ray)(ref val2)).GetPoint(distance);
			}
			return null;
		}

		private int CountMirrorableBuffers()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (CameraEvent value in Enum.GetValues(typeof(CameraEvent)))
			{
				CommandBuffer[] commandBuffers = taleSpireCamera.GetCommandBuffers(value);
				foreach (CommandBuffer cb in commandBuffers)
				{
					if (!SkipBufferPrefixes.Any((string p) => (cb.name ?? "").StartsWith(p)))
					{
						num++;
					}
				}
			}
			return num;
		}

		private void ScanEyeCameras()
		{
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			TileRenderHooks.EyeCameras.RemoveAll((Camera c) => (Object)(object)c == (Object)null);
			configuredEyes.RemoveWhere((Camera c) => (Object)(object)c == (Object)null);
			foreach (Camera item in mirroredBuffers.Keys.Where((Camera c) => (Object)(object)c == (Object)null).ToList())
			{
				mirroredBuffers.Remove(item);
			}
			if (plugin.MirrorCommandBuffers.Value && (Object)(object)taleSpireCamera != (Object)null)
			{
				int num = CountMirrorableBuffers();
				if (mirroredSourceCount >= 0 && num != mirroredSourceCount)
				{
					TaleSpireTiltFivePlugin.Log.LogInfo((object)$"TaleSpire camera buffers changed ({mirroredSourceCount} -> {num}); re-mirroring onto {configuredEyes.Count} eye camera(s).");
					foreach (Camera configuredEye in configuredEyes)
					{
						MirrorBuffers(configuredEye);
					}
				}
				mirroredSourceCount = num;
			}
			Camera[] array = Resources.FindObjectsOfTypeAll<Camera>();
			foreach (Camera val in array)
			{
				if ((Object)(object)val == (Object)null || configuredEyes.Contains(val))
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).name == null && (int)((Object)((Component)val).gameObject).hideFlags == 0)
				{
					continue;
				}
				string name = ((Object)((Component)val).gameObject).name;
				if (!(name != "Left Eye Camera") || !(name != "Right Eye Camera"))
				{
					CameraUtil.ApplyLook(val, taleSpireCamera);
					if (!TileRenderHooks.EyeCameras.Contains(val))
					{
						TileRenderHooks.EyeCameras.Add(val);
					}
					if (plugin.MirrorCommandBuffers.Value)
					{
						MirrorBuffers(val);
					}
					configuredEyes.Add(val);
					TaleSpireTiltFivePlugin.Log.LogInfo((object)("Configured eye camera '" + name + "' (target " + (Object.op_Implicit((Object)(object)val.targetTexture) ? (((Texture)val.targetTexture).width + "x" + ((Texture)val.targetTexture).height) : "none") + ")."));
					if (plugin.VerboseDiagnostics.Value && configuredEyes.Count == 1)
					{
						Diagnostics.LogCommandBuffers(taleSpireCamera, "TaleSpire camera");
					}
				}
			}
		}

		private void MirrorBuffers(Camera eye)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			if (mirroredBuffers.TryGetValue(eye, out var value))
			{
				foreach (KeyValuePair<CameraEvent, CommandBuffer> item in value)
				{
					eye.RemoveCommandBuffer(item.Key, item.Value);
				}
			}
			List<KeyValuePair<CameraEvent, CommandBuffer>> list = new List<KeyValuePair<CameraEvent, CommandBuffer>>();
			foreach (CameraEvent value2 in Enum.GetValues(typeof(CameraEvent)))
			{
				CommandBuffer[] commandBuffers = taleSpireCamera.GetCommandBuffers(value2);
				foreach (CommandBuffer cb in commandBuffers)
				{
					if (!SkipBufferPrefixes.Any((string p) => (cb.name ?? "").StartsWith(p)))
					{
						eye.AddCommandBuffer(value2, cb);
						list.Add(new KeyValuePair<CameraEvent, CommandBuffer>(value2, cb));
					}
				}
			}
			mirroredBuffers[eye] = list;
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Mirrored {list.Count} command buffer(s) onto '{((Object)eye).name}'.");
		}

		private void ReportConnection()
		{
			for (int i = 0; i < PlayerCountClamped; i++)
			{
				bool flag = false;
				try
				{
					flag = Player.IsConnected((PlayerIndex)(i + 1));
				}
				catch
				{
				}
				if (flag != wasConnected[i])
				{
					wasConnected[i] = flag;
					TaleSpireTiltFivePlugin.Log.LogInfo((object)(flag ? $"Tilt Five glasses connected (player {i + 1})." : $"Tilt Five glasses disconnected (player {i + 1})."));
				}
			}
		}
	}
	internal static class Keys
	{
		private static bool legacyBroken;

		private static readonly Dictionary<KeyCode, Key> map = new Dictionary<KeyCode, Key>();

		internal static bool Pressed(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Query(key, held: false);
		}

		internal static bool Held(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Query(key, held: true);
		}

		private static bool Query(KeyCode key, bool held)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Keyboard current = Keyboard.current;
				if (current != null)
				{
					Key val = ToInputSystemKey(key);
					if ((int)val != 0)
					{
						return held ? ((ButtonControl)current[val]).isPressed : ((ButtonControl)current[val]).wasPressedThisFrame;
					}
				}
			}
			catch
			{
			}
			if (legacyBroken)
			{
				return false;
			}
			try
			{
				return held ? Input.GetKey(key) : Input.GetKeyDown(key);
			}
			catch (InvalidOperationException)
			{
				legacyBroken = true;
				return false;
			}
		}

		private unsafe static Key ToInputSystemKey(KeyCode key)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: 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)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (!map.TryGetValue(key, out var value))
			{
				if (!Enum.TryParse<Key>(((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(), out value))
				{
					value = (Key)0;
				}
				map[key] = value;
			}
			return value;
		}
	}
	internal static class PostProcessCloner
	{
		internal static void Retarget(GameObject target, Camera source)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PostProcessLayer component = target.GetComponent<PostProcessLayer>();
				if (!((Object)(object)component == (Object)null))
				{
					PostProcessLayer component2 = ((Component)source).GetComponent<PostProcessLayer>();
					component.volumeTrigger = ((Component)source).transform;
					if ((Object)(object)component2 != (Object)null)
					{
						component.volumeLayer = component2.volumeLayer;
						component.fog = component2.fog;
					}
				}
			}
			catch (Exception ex)
			{
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("PostProcessLayer retarget failed: " + ex.Message));
			}
		}

		internal static void TryClone(Camera source, GameObject target)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PostProcessLayer component = ((Component)source).GetComponent<PostProcessLayer>();
				if ((Object)(object)component == (Object)null)
				{
					TaleSpireTiltFivePlugin.Log.LogInfo((object)"TaleSpire camera has no PostProcessLayer; eye cameras will render without post-processing.");
					return;
				}
				PostProcessLayer val = target.AddComponent<PostProcessLayer>();
				val.volumeLayer = component.volumeLayer;
				val.volumeTrigger = ((Component)source).transform;
				val.stopNaNPropagation = component.stopNaNPropagation;
				val.finalBlitToCameraTarget = false;
				val.antialiasingMode = (Antialiasing)0;
				val.fog = component.fog;
				object? obj = typeof(PostProcessLayer).GetField("m_Resources", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(component);
				PostProcessResources val2 = (PostProcessResources)((obj is PostProcessResources) ? obj : null);
				if ((Object)(object)val2 != (Object)null)
				{
					val.Init(val2);
					TaleSpireTiltFivePlugin.Log.LogInfo((object)$"Cloned PostProcessLayer (volume layer mask {((LayerMask)(ref val.volumeLayer)).value}).");
				}
				else
				{
					Object.DestroyImmediate((Object)(object)val);
					TaleSpireTiltFivePlugin.Log.LogWarning((object)"Could not read PostProcessResources from TaleSpire's camera; skipping post-processing on eye cameras.");
				}
			}
			catch (Exception ex)
			{
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("PostProcessLayer clone failed: " + ex.Message));
			}
		}
	}
	internal static class LogFix
	{
		private static bool announced;

		internal static void Apply()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			ILogger unityLogger = Debug.unityLogger;
			if (unityLogger.logEnabled && (int)unityLogger.filterLogType == 3)
			{
				return;
			}
			unityLogger.logEnabled = true;
			unityLogger.filterLogType = (LogType)3;
			if (!announced)
			{
				announced = true;
				ManualLogSource log = TaleSpireTiltFivePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"Re-enabled Unity logging (TaleSpire had it off) so SDK messages and exceptions are visible.");
				}
			}
		}
	}
	internal static class Diagnostics
	{
		internal static void LogT5Status(GameBoard board, Camera tsCam, Camera spectator)
		{
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			ManualLogSource log = TaleSpireTiltFivePlugin.Log;
			string text = "?";
			try
			{
				text = SystemControl.GetServiceCompatibility().ToString();
			}
			catch (Exception ex)
			{
				text = "err:" + ex.GetType().Name;
			}
			bool flag = false;
			bool flag2 = false;
			bool flag3 = false;
			try
			{
				flag = Player.TryGetGlassesHandle(PlayerIndex.One, out var _);
			}
			catch
			{
			}
			try
			{
				flag2 = Player.IsConnected(PlayerIndex.One);
			}
			catch
			{
			}
			try
			{
				flag3 = Glasses.IsTracked();
			}
			catch
			{
			}
			List<string> list = new List<string>();
			for (int i = 1; i <= 4; i++)
			{
				bool flag4 = false;
				bool flag5 = false;
				try
				{
					flag4 = Player.IsConnected((PlayerIndex)i);
					flag5 = flag4 && Glasses.IsTracked((PlayerIndex)i);
				}
				catch
				{
				}
				if (flag4)
				{
					list.Add(string.Format("P{0}{1}", i, flag5 ? "+" : "-"));
				}
			}
			IEnumerable<string> values = from c in Resources.FindObjectsOfTypeAll<Camera>().Where(delegate(Camera c)
				{
					//IL_0033: Unknown result type (might be due to invalid IL or missing references)
					//IL_0038: Unknown result type (might be due to invalid IL or missing references)
					if ((Object)(object)c != (Object)null && (((Object)c).name == "Left Eye Camera" || ((Object)c).name == "Right Eye Camera"))
					{
						Scene scene = ((Component)c).gameObject.scene;
						return ((Scene)(ref scene)).IsValid();
					}
					return false;
				})
				select string.Format("{0}[en={1} rt={2} pos={3:F1} mask=0x{4:X}]", ((Object)c).name, ((Behaviour)c).isActiveAndEnabled, Object.op_Implicit((Object)(object)c.targetTexture) ? (((Texture)c.targetTexture).width + "x" + ((Texture)c.targetTexture).height) : "none", ((Component)c).transform.position, c.cullingMask);
			int num = (((Object)(object)spectator != (Object)null) ? ((Component)spectator).GetComponents<SplitStereoCamera>().Length : (-1));
			string[] obj5 = new string[7]
			{
				string.Format("[status] players=[{0}] service={1} handle={2} connected={3} tracked={4} splitStereo={5} ", string.Join(" ", list), text, flag, flag2, flag3, num),
				"board=",
				null,
				null,
				null,
				null,
				null
			};
			object obj6;
			Vector3 position;
			if (!Object.op_Implicit((Object)(object)board))
			{
				obj6 = "null";
			}
			else
			{
				position = ((Component)board).transform.position;
				obj6 = ((Vector3)(ref position)).ToString("F1") + " x" + board.localScale.ToString("0.##");
			}
			obj5[2] = (string)obj6;
			obj5[3] = " tsCam=";
			object obj7;
			if (!Object.op_Implicit((Object)(object)tsCam))
			{
				obj7 = "null";
			}
			else
			{
				string name = ((Object)tsCam).name;
				position = ((Component)tsCam).transform.position;
				obj7 = name + "@" + ((Vector3)(ref position)).ToString("F1");
			}
			obj5[4] = (string)obj7;
			obj5[5] = " eyes: ";
			obj5[6] = string.Join(" ", values);
			log.LogInfo((object)string.Concat(obj5));
		}

		internal static void LogCameras()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			ManualLogSource log = TaleSpireTiltFivePlugin.Log;
			Camera[] array = Resources.FindObjectsOfTypeAll<Camera>();
			foreach (Camera val in array)
			{
				Scene scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).name != null)
				{
					string text = string.Join(", ", from x in ((Component)val).GetComponents<Component>()
						select (!((Object)(object)x == (Object)null)) ? ((object)x).GetType().Name : "null");
					log.LogInfo((object)$"[cam] '{((Object)val).name}' active={((Behaviour)val).isActiveAndEnabled} depth={val.depth} tag={((Component)val).tag} mask=0x{val.cullingMask:X} rt={(Object)(object)val.targetTexture != (Object)null} path={val.renderingPath} comps=[{text}]");
				}
			}
		}

		internal static void LogCommandBuffers(Camera cam, string label)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			ManualLogSource log = TaleSpireTiltFivePlugin.Log;
			int num = 0;
			foreach (CameraEvent value in Enum.GetValues(typeof(CameraEvent)))
			{
				CommandBuffer[] commandBuffers = cam.GetCommandBuffers(value);
				foreach (CommandBuffer val2 in commandBuffers)
				{
					log.LogInfo((object)$"[cmdbuf] {label} {(object)value}: '{val2.name}' ({val2.sizeInBytes} bytes)");
					num++;
				}
			}
			log.LogInfo((object)$"[cmdbuf] {label}: {num} buffer(s) total.");
		}

		internal static void LogCameraControllerMembers()
		{
			ManualLogSource log = TaleSpireTiltFivePlugin.Log;
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					string name = assembly.GetName().Name;
					if (!name.StartsWith("Bouncyrock") && name != "Assembly-CSharp")
					{
						continue;
					}
					Type[] source;
					try
					{
						source = assembly.GetTypes();
					}
					catch (ReflectionTypeLoadException ex)
					{
						source = ex.Types.Where((Type t) => t != null).ToArray();
					}
					foreach (Type item in source.Where((Type t) => t.Name == "CameraController" || t.Name == "ActiveCameraManager"))
					{
						log.LogInfo((object)("[ctrl] " + item.FullName + " in " + name));
						foreach (FieldInfo item2 in item.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Take(60))
						{
							log.LogInfo((object)("[ctrl]   field " + (item2.IsStatic ? "static " : "") + item2.FieldType.Name + " " + item2.Name));
						}
						foreach (PropertyInfo item3 in item.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Take(60))
						{
							log.LogInfo((object)("[ctrl]   prop " + item3.PropertyType.Name + " " + item3.Name));
						}
					}
				}
			}
			catch (Exception ex2)
			{
				log.LogWarning((object)("Camera controller reflection failed: " + ex2.Message));
			}
		}
	}
	internal class SessionFeatures : MonoBehaviour
	{
		internal WandInteraction Wands;

		internal Func<int> GmSeat = () => 1;

		internal Func<float> WorldPerTile = () => 1f;

		internal string StartPointsFile;

		private string currentTurnKey;

		private float nextTurnPoll;

		private LineRenderer turnRing;

		private bool initiativeBroken;

		private readonly Dictionary<string, Vector3> startPoints = new Dictionary<string, Vector3>();

		internal CreatureBoardAsset CurrentTurn { get; private set; }

		internal bool HasStartPoint
		{
			get
			{
				string text = SafeBoardKey();
				if (text != null)
				{
					return startPoints.ContainsKey(text);
				}
				return false;
			}
		}

		internal Vector3? StartPoint
		{
			get
			{
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				string text = SafeBoardKey();
				if (text == null || !startPoints.TryGetValue(text, out var value))
				{
					return null;
				}
				return value;
			}
		}

		private static string SafeName(CreatureBoardAsset c)
		{
			try
			{
				return c.Name;
			}
			catch
			{
				return ((Object)(object)c != (Object)null) ? ((Object)c).name : "?";
			}
		}

		internal void Init()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			Shader val = Shader.Find("Hidden/Internal-Colored");
			if ((Object)(object)val != (Object)null)
			{
				Material val2 = new Material(val)
				{
					hideFlags = (HideFlags)61,
					renderQueue = 3100
				};
				val2.SetInt("_SrcBlend", 5);
				val2.SetInt("_DstBlend", 10);
				val2.SetInt("_ZWrite", 0);
				val2.SetInt("_Cull", 0);
				GameObject val3 = new GameObject("T5 Turn Ring");
				val3.transform.SetParent(((Component)this).transform, false);
				turnRing = val3.AddComponent<LineRenderer>();
				((Renderer)turnRing).sharedMaterial = val2;
				turnRing.useWorldSpace = true;
				turnRing.loop = true;
				turnRing.positionCount = 48;
				((Renderer)turnRing).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)turnRing).receiveShadows = false;
				((Renderer)turnRing).enabled = false;
			}
			LoadStartPoints();
		}

		private void Update()
		{
			if (Time.unscaledTime >= nextTurnPoll && !initiativeBroken)
			{
				nextTurnPoll = Time.unscaledTime + 0.25f;
				PollTurn();
			}
			DrawTurnRing();
		}

		private void PollTurn()
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			string text = null;
			try
			{
				text = CurrentTurnCreatureKey();
			}
			catch (Exception ex)
			{
				initiativeBroken = true;
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("Turn indicator off: TaleSpire's initiative tracker isn't readable (" + ex.GetType().Name + ": " + ex.Message + ")."));
				return;
			}
			if (text == currentTurnKey)
			{
				return;
			}
			currentTurnKey = text;
			CurrentTurn = null;
			if (text != null && (Object)(object)Wands != (Object)null)
			{
				foreach (CreatureBoardAsset creature in Wands.CreatureList)
				{
					try
					{
						if ((Object)(object)creature != (Object)null && ((object)creature.CreatureId/*cast due to .constrained prefix*/).ToString() == text)
						{
							CurrentTurn = creature;
							break;
						}
					}
					catch
					{
					}
				}
			}
			if (!((Object)(object)CurrentTurn == (Object)null))
			{
				string text2 = SafeName(CurrentTurn);
				int num = Wands.OwnerOfCreature(CurrentTurn);
				bool flag = false;
				flag = PlayerVisibility.IsHiddenFromPlayers(CurrentTurn);
				TaleSpireTiltFivePlugin.Log.LogInfo((object)("[turn] " + text2 + "'s turn" + ((num > 0) ? $" (player {num})" : "") + (flag ? ", hidden" : "") + "."));
				Wands.FlashSeat(GmSeat(), "Turn: " + text2, 3f);
				if (num > 0 && num != GmSeat())
				{
					Wands.FlashSeat(num, "Your turn: " + text2, 4f);
					Wands.PulseSeat(num, 0.6f, 0.1f);
					((MonoBehaviour)this).StartCoroutine(SecondPulse(num));
				}
			}
		}

		private IEnumerator SecondPulse(int seat)
		{
			yield return (object)new WaitForSecondsRealtime(0.25f);
			Wands.PulseSeat(seat, 0.6f, 0.1f);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private unsafe static string CurrentTurnCreatureKey()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			List<QueueElement> turnQueueList = InitiativeManager.TurnQueueList;
			if (turnQueueList == null || turnQueueList.Count == 0)
			{
				return null;
			}
			QueueElement currentTurnElement = InitiativeManager.CurrentTurnElement;
			if ((int)currentTurnElement.Type != 0)
			{
				return null;
			}
			return ((object)(*(CreatureGuid*)(&currentTurnElement.CreatureGuid))/*cast due to .constrained prefix*/).ToString();
		}

		private void DrawTurnRing()
		{
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)turnRing == (Object)null)
			{
				return;
			}
			CreatureBoardAsset currentTurn = CurrentTurn;
			bool flag = false;
			flag = PlayerVisibility.IsHiddenFromPlayers(currentTurn);
			if ((Object)(object)currentTurn == (Object)null || !((Behaviour)currentTurn).isActiveAndEnabled || flag || ((Object)(object)Wands != (Object)null && Wands.IsHeld(currentTurn)))
			{
				((Renderer)turnRing).enabled = false;
				return;
			}
			float num = Mathf.Max(0.0001f, WorldPerTile());
			float num2 = 0.55f * num;
			try
			{
				Bounds creatureBounds = currentTurn.CreatureBounds;
				if (((Bounds)(ref creatureBounds)).size.y > 0.05f)
				{
					num2 = Mathf.Clamp(Mathf.Max(((Bounds)(ref creatureBounds)).extents.x, ((Bounds)(ref creatureBounds)).extents.z), 0.25f, 5f) * 1.35f;
				}
			}
			catch
			{
			}
			float num3 = 0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * 4f);
			Color val = default(Color);
			((Color)(ref val))..ctor(1f, 0.82f, 0.25f, 0.55f + 0.45f * num3);
			LineRenderer obj2 = turnRing;
			Color startColor = (turnRing.endColor = val);
			obj2.startColor = startColor;
			turnRing.widthMultiplier = (0.05f + 0.03f * num3) * num;
			Vector3 position = ((Component)currentTurn).transform.position;
			int positionCount = turnRing.positionCount;
			for (int i = 0; i < positionCount; i++)
			{
				float num4 = (float)i * (float)Math.PI * 2f / (float)positionCount;
				turnRing.SetPosition(i, position + new Vector3(Mathf.Cos(num4) * num2, 0.03f * num, Mathf.Sin(num4) * num2));
			}
			((Renderer)turnRing).enabled = true;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static string BoardKey()
		{
			return ((object)Unsafe.As<BoardGuid, BoardGuid>(ref BoardSessionManager.CurrentBoardInfo.Id)/*cast due to .constrained prefix*/).ToString();
		}

		private static string SafeBoardKey()
		{
			try
			{
				return BoardKey();
			}
			catch
			{
				return null;
			}
		}

		internal string SetStartPoint(Vector3 at)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			string text = SafeBoardKey();
			if (text == null)
			{
				return "! no board loaded";
			}
			startPoints[text] = at;
			SaveStartPoints();
			float num = Mathf.Max(0.0001f, WorldPerTile());
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"[start] party start for board {text.Substring(0, Math.Min(8, text.Length))} set at {at}.");
			return string.Format(CultureInfo.InvariantCulture, "Party start set at ({0:0.#}, {1:0.#})", at.x / num, at.z / num);
		}

		internal IEnumerator GatherParty(Action<string> done)
		{
			Vector3? start = StartPoint;
			if (!start.HasValue)
			{
				done("! no party start on this board yet (menu: Set party start here)");
				yield break;
			}
			if (!TaleSpireOps.CanEdit(out var why))
			{
				done("! can't move minis: " + why);
				yield break;
			}
			float tile = Mathf.Max(0.0001f, WorldPerTile());
			List<CreatureBoardAsset> party = (from c in Wands.CreatureList
				where (Object)(object)c != (Object)null && Wands.OwnerOfCreature(c) >= 1 && Wands.OwnerOfCreature(c) != GmSeat() && !Wands.IsHeld(c)
				orderby Wands.OwnerOfCreature(c), SafeName(c)
				select c).ToList();
			if (party.Count == 0)
			{
				done("! no minis are assigned to players yet");
				yield break;
			}
			List<Vector2> spots = Spiral(party.Count);
			int moved = 0;
			for (int i = 0; i < party.Count; i++)
			{
				CreatureBoardAsset val = party[i];
				Vector3 val2 = start.Value + new Vector3(spots[i].x * tile, 0f, spots[i].y * tile);
				val2.y = ((Component)val).transform.position.y;
				try
				{
					val.Pickup();
					Vector3 val3 = val.FindLegalDropPos(val2);
					val.Drop(val3, val3.y);
					moved++;
				}
				catch (Exception ex)
				{
					TaleSpireTiltFivePlugin.Log.LogWarning((object)("[start] couldn't move " + SafeName(val) + ": " + ex.Message));
				}
				yield return null;
			}
			TaleSpireTiltFivePlugin.Log.LogInfo((object)$"[start] gathered {moved} of {party.Count} party mini(s) at the start.");
			done((moved > 0) ? $"Party gathered ({moved})" : "! couldn't move the party");
		}

		private static List<Vector2> Spiral(int n)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			List<Vector2> list = new List<Vector2>();
			int num = 0;
			while (list.Count < n && num < 10)
			{
				for (int i = -num; i <= num; i++)
				{
					for (int j = -num; j <= num; j++)
					{
						if (Mathf.Max(Mathf.Abs(i), Mathf.Abs(j)) == num)
						{
							list.Add(new Vector2((float)i, (float)j));
						}
					}
				}
				num++;
			}
			return list;
		}

		private void LoadStartPoints()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (StartPointsFile == null || !File.Exists(StartPointsFile))
				{
					return;
				}
				string[] array = File.ReadAllLines(StartPointsFile);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length == 0 || text.StartsWith("#"))
					{
						continue;
					}
					string[] array2 = text.Split('=');
					if (array2.Length == 2)
					{
						float[] array3 = (from v in array2[1].Split(',')
							select float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
						if (array3.Length == 3)
						{
							startPoints[array2[0].Trim()] = new Vector3(array3[0], array3[1], array3[2]);
						}
					}
				}
			}
			catch (Exception ex)
			{
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("Could not read start points: " + ex.Message));
			}
		}

		private void SaveStartPoints()
		{
			try
			{
				if (StartPointsFile != null)
				{
					List<string> list = new List<string> { "# TaleSpire Tilt Five: party start point per board (board id = x,y,z in world units)" };
					list.AddRange(startPoints.Select((KeyValuePair<string, Vector3> kv) => string.Format(CultureInfo.InvariantCulture, "{0}={1},{2},{3}", kv.Key, kv.Value.x, kv.Value.y, kv.Value.z)));
					File.WriteAllLines(StartPointsFile, list);
				}
			}
			catch (Exception ex)
			{
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("Could not save start points: " + ex.Message));
			}
		}

		internal IEnumerator Roll(string roll, string label, bool gmHidden, Action<string> done)
		{
			roll = (roll ?? "1d20").Replace(" ", "").ToLowerInvariant();
			if (!Regex.IsMatch(roll, "^(\\d*d\\d+|\\d+)([+-](\\d*d\\d+|\\d+))*$"))
			{
				done("! '" + roll + "' isn't a dice roll TaleSpire understands");
				yield break;
			}
			string name = (string.IsNullOrWhiteSpace(label) ? roll : label.Trim());
			string url = "talespire://dice/" + Uri.EscapeDataString(name) + ":" + roll;
			string err = null;
			try
			{
				LoadDice(url);
			}
			catch (Exception ex)
			{
				err = ex.Message;
			}
			if (err != null)
			{
				done("! dice failed: " + err);
				yield break;
			}
			yield return null;
			yield return null;
			try
			{
				Throw(gmHidden);
			}
			catch (Exception ex2)
			{
				err = ex2.Message;
			}
			TaleSpireTiltFivePlugin.Log.LogInfo((object)("[dice] " + name + ": " + roll + (gmHidden ? " (GM hidden)" : "") + ((err != null) ? (" - throw failed: " + err) : "") + "."));
			done((err == null) ? ("Rolling " + roll + (gmHidden ? " (hidden)" : "")) : ("Dice loaded in the tray (" + roll + "); throw failed: " + err));
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void LoadDice(string url)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			LocalConnectionManager.ProcessTaleSpireUrl(url);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void Throw(bool gmHidden)
		{
			UIDiceTray diceTray = GUIManager.DiceTray;
			if ((Object)(object)diceTray == (Object)null)
			{
				throw new InvalidOperationException("no dice tray");
			}
			if (gmHidden)
			{
				diceTray.SpawnGMDice();
			}
			else
			{
				diceTray.SpawnDice();
			}
		}

		internal string Describe(Func<CreatureBoardAsset, string> idOf)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			if ((Object)(object)CurrentTurn != (Object)null)
			{
				list.Add("Current turn (TaleSpire initiative): " + (idOf(CurrentTurn) ?? "?") + " \"" + SafeName(CurrentTurn) + "\".");
			}
			Vector3? startPoint = StartPoint;
			float num = Mathf.Max(0.0001f, WorldPerTile());
			if (startPoint.HasValue)
			{
				list.Add(string.Format(CultureInfo.InvariantCulture, "Party start point: ({0:0.#}, {1:0.#}).", startPoint.Value.x / num, startPoint.Value.z / num));
			}
			return string.Join(" ", list);
		}
	}
	internal static class PlayerVisibility
	{
		internal static Func<int> GmSeat = () => 1;

		private static bool volumeApiBroken;

		internal static bool VolumeApiOk => !volumeApiBroken;

		internal static bool IsPlayerEye(Camera cam)
		{
			int num = SeatCameras.SeatOf(cam);
			if (num != 0)
			{
				return num != GmSeat();
			}
			return false;
		}

		internal static bool IsHiddenFromPlayers(CreatureBoardAsset c)
		{
			if ((Object)(object)c == (Object)null)
			{
				return false;
			}
			try
			{
				if (c.IsExplicitlyHidden)
				{
					return true;
				}
			}
			catch
			{
			}
			return IsHiddenByVolume(c);
		}

		internal static bool IsHiddenByVolume(CreatureBoardAsset c)
		{
			if (volumeApiBroken || (Object)(object)c == (Object)null)
			{
				return false;
			}
			try
			{
				return VolumeInner(c);
			}
			catch (Exception ex)
			{
				volumeApiBroken = true;
				TaleSpireTiltFivePlugin.Log.LogWarning((object)("Hide-volume creature check unavailable (" + ex.GetType().Name + ": " + ex.Message + "); only GM-hidden creatures are filtered."));
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool VolumeInner(CreatureBoardAsset c)
		{
			//IL_0001: Unknown result type (might