Decompiled source of GhostSpectator v0.2.0

plugins/SisyphusMD.GhostSpectator.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using GhostSpectator.Patches;
using GhostSpectator.Runtime;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SisyphusMD.GhostSpectator")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+4f459ec7fe975aba9e7341a951a4458802675bb3")]
[assembly: AssemblyProduct("SisyphusMD.GhostSpectator")]
[assembly: AssemblyTitle("GhostSpectator")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://forgejo.bryantserver.com/SisyphusMD/GhostSpectator")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace GhostSpectator
{
	public static class PatchValidator
	{
		public static (int ok, int missing) Validate(Action<string> logError)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Invalid comparison between Unknown and I4
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Invalid comparison between Unknown and I4
			int num = 0;
			int num2 = 0;
			Type[] types = typeof(Plugin).Assembly.GetTypes();
			foreach (Type type in types)
			{
				object[] customAttributes = type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false);
				if (customAttributes.Length == 0)
				{
					continue;
				}
				HarmonyPatch val = (HarmonyPatch)customAttributes[0];
				HarmonyMethod info = ((HarmonyAttribute)val).info;
				if (info.declaringType == null || info.methodName == null)
				{
					MethodInfo methodInfo = AccessTools.Method(type, "TargetMethod", (Type[])null, (Type[])null);
					if (methodInfo == null)
					{
						logError("[validate] " + type.Name + ": [HarmonyPatch] has no target info AND no TargetMethod() dispatcher");
						num2++;
						continue;
					}
					try
					{
						MethodBase methodBase = (MethodBase)methodInfo.Invoke(null, null);
						if (methodBase != null)
						{
							num++;
							continue;
						}
						logError("[validate] " + type.Name + ": TargetMethod() returned null");
						num2++;
					}
					catch (Exception ex)
					{
						logError("[validate] " + type.Name + ": TargetMethod() threw " + ex.GetType().Name + ": " + ex.Message);
						num2++;
					}
				}
				else
				{
					MethodBase methodBase2 = (((int)info.methodType.GetValueOrDefault() == 1) ? AccessTools.PropertyGetter(info.declaringType, info.methodName) : (((int)info.methodType.GetValueOrDefault() != 2) ? AccessTools.Method(info.declaringType, info.methodName, info.argumentTypes, (Type[])null) : AccessTools.PropertySetter(info.declaringType, info.methodName)));
					if (methodBase2 == null)
					{
						logError($"[validate] {type.Name}: TARGET NOT FOUND: {info.declaringType.FullName}.{info.methodName} (methodType={info.methodType})");
						num2++;
					}
					else
					{
						num++;
					}
				}
			}
			(string, Type, string)[] array = new(string, Type, string)[18]
			{
				("Character.AllCharacters (static field)", typeof(Character), "AllCharacters"),
				("Character.localCharacter (static field)", typeof(Character), "localCharacter"),
				("Character.refs (field)", typeof(Character), "refs"),
				("Character.CharacterRefs.ragdoll (field)", typeof(CharacterRefs), "ragdoll"),
				("CharacterAfflictions.character (field)", typeof(CharacterAfflictions), "character"),
				("CharacterData.fullyPassedOut (field)", typeof(CharacterData), "fullyPassedOut"),
				("CharacterData.deathTimer (field)", typeof(CharacterData), "deathTimer"),
				("CharacterData.character (field)", typeof(CharacterData), "character"),
				("GUIManager.dyingBarObject (field)", typeof(GUIManager), "dyingBarObject"),
				("GUIManager.staminaCanvasGroup (field)", typeof(GUIManager), "staminaCanvasGroup"),
				("GUIManager.mushroomsCanvasGroup (field)", typeof(GUIManager), "mushroomsCanvasGroup"),
				("GUIManager.items (field)", typeof(GUIManager), "items"),
				("GUIManager.backpack (field)", typeof(GUIManager), "backpack"),
				("PointPinger.character (field)", typeof(PointPinger), "character"),
				("PointPinger.pingInstance (field)", typeof(PointPinger), "pingInstance"),
				("PointPinger.pointPrefab (field)", typeof(PointPinger), "pointPrefab"),
				("PointPinger.coolDown (field)", typeof(PointPinger), "coolDown"),
				("PointPinger._timeLastPinged (field)", typeof(PointPinger), "_timeLastPinged")
			};
			(string, Type, string)[] array2 = array;
			for (int j = 0; j < array2.Length; j++)
			{
				var (text, type2, text2) = array2[j];
				if (AccessTools.Field(type2, text2) != null)
				{
					num++;
					continue;
				}
				logError("[validate] " + text + ": NOT FOUND");
				num2++;
			}
			(string, Type, string)[] array3 = new(string, Type, string)[4]
			{
				("CharacterData.dead (property)", typeof(CharacterData), "dead"),
				("CharacterData.canBeSpectated (property)", typeof(CharacterData), "canBeSpectated"),
				("MainCameraMovement.specCharacter (static property)", typeof(MainCameraMovement), "specCharacter"),
				("PhotonNetwork.PlayerList (property)", typeof(PhotonNetwork), "PlayerList")
			};
			(string, Type, string)[] array4 = array3;
			for (int k = 0; k < array4.Length; k++)
			{
				var (text3, type3, text4) = array4[k];
				if (AccessTools.Property(type3, text4) != null)
				{
					num++;
					continue;
				}
				logError("[validate] " + text3 + ": NOT FOUND");
				num2++;
			}
			return (num, num2);
		}
	}
	[BepInPlugin("SisyphusMD.GhostSpectator", "GhostSpectator", "0.2.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Id = "SisyphusMD.GhostSpectator";

		internal static ManualLogSource Log { get; private set; }

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

		public static string Name => "GhostSpectator";

		public static string Version => "0.2.0";

		internal static void Trace(string msg)
		{
			Log.LogInfo((object)msg);
			Debug.Log((object)("[GhostSpectator] " + msg));
		}

		internal static void TraceWarn(string msg)
		{
			Log.LogWarning((object)msg);
			Debug.LogWarning((object)("[GhostSpectator] " + msg));
		}

		internal static void TraceDebug(string msg)
		{
			Log.LogDebug((object)msg);
		}

		private void Awake()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Expected O, but got Unknown
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SpectatorEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Spectator", "Enabled", false, "Toggled in-game via the 'Ghost Spectator' button in the airport lobby. When true, this player spawns into every match as a permanent ghost: dead from the start, with the vanilla ghost camera and voice chat, and never revived by Scout Effigies, Ancient Statues, or Respawn Chests. Other players are unaffected. Don't edit this directly, use the in-game button so the change broadcasts to your lobby properly.");
			(int, int) tuple = PatchValidator.Validate((Action<string>)Log.LogError);
			Log.LogInfo((object)$"[validate] {tuple.Item1} targets resolved, {tuple.Item2} missing");
			Harmony val = new Harmony("SisyphusMD.GhostSpectator");
			int num = 0;
			int num2 = 0;
			Type[] types = typeof(Plugin).Assembly.GetTypes();
			foreach (Type type in types)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length == 0)
				{
					continue;
				}
				try
				{
					val.CreateClassProcessor(type).Patch();
					Log.LogInfo((object)("[trace] attached: " + type.Name));
					num++;
				}
				catch (Exception ex)
				{
					Log.LogError((object)("[trace] FAILED to attach " + type.Name + ": " + ex.GetType().Name + ": " + ex.Message));
					if (ex.InnerException != null)
					{
						Log.LogError((object)("[trace]   inner: " + ex.InnerException.GetType().Name + ": " + ex.InnerException.Message));
					}
					num2++;
				}
			}
			Log.LogInfo((object)$"[trace] patches attached: {num}, failed: {num2}");
			if (num == 0)
			{
				Log.LogError((object)"GhostSpectator: no patches attached. Mod is non-functional.");
				return;
			}
			GameObject val2 = new GameObject("GhostSpectator.Runtime");
			Object.DontDestroyOnLoad((Object)(object)val2);
			val2.AddComponent<RoomCallbackHandler>();
			val2.AddComponent<SpectatorMenuUI>();
			val2.AddComponent<MidRunJoinPopup>();
			Log.LogInfo((object)$"{Name} {Version} loaded, spectator={SpectatorEnabled.Value}");
		}
	}
}
namespace GhostSpectator.Runtime
{
	internal static class GuiHelpers
	{
		internal static Texture2D BuildSolidTexture(Color color)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0019: Expected O, but got Unknown
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1,
				hideFlags = (HideFlags)61
			};
			val.SetPixel(0, 0, color);
			val.Apply();
			return val;
		}

		internal static GUIStyle MakeLabelStyle(int fontSize, FontStyle fontStyle, TextAnchor alignment, Color textColor, bool wordWrap = false)
		{
			//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_0016: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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_002d: Expected O, but got Unknown
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = fontSize,
				fontStyle = fontStyle,
				alignment = alignment,
				wordWrap = wordWrap
			};
			val.normal.textColor = textColor;
			return val;
		}

		internal static Texture2D BuildGhostTexture(int size)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: 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_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: 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)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1,
				hideFlags = (HideFlags)61
			};
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = Color.clear;
			}
			float num = (float)size / 2f;
			float num2 = (float)size * 0.42f;
			float num3 = (float)size * 0.45f;
			float num4 = (float)size * 0.82f;
			float num5 = num2 / 2.6f;
			for (int j = 0; j < size; j++)
			{
				float num6 = (float)j + 0.5f;
				int num7 = size - 1 - j;
				for (int k = 0; k < size; k++)
				{
					float num8 = (float)k + 0.5f;
					float num9 = num8 - num;
					bool flag = false;
					if (num6 < num3)
					{
						float num10 = (num6 - num3) / num3;
						float num11 = num9 / num2;
						if (num11 * num11 + num10 * num10 <= 1f)
						{
							flag = true;
						}
					}
					else if (num6 < num4)
					{
						if (Mathf.Abs(num9) <= num2)
						{
							flag = true;
						}
					}
					else if (Mathf.Abs(num9) <= num2)
					{
						int num12 = 3;
						float num13 = num2 * 2f / (float)num12;
						for (int l = 0; l < num12; l++)
						{
							float num14 = 0f - num2 + ((float)l + 0.5f) * num13;
							float num15 = num9 - num14;
							float num16 = num6 - num4;
							if (num15 * num15 + num16 * num16 <= num5 * num5)
							{
								flag = true;
								break;
							}
						}
					}
					if (flag)
					{
						array[num7 * size + k] = Color.white;
					}
				}
			}
			DrawDisk(array, size, (int)((float)size * 0.34f), (int)((float)size * 0.42f), Mathf.Max(2, size / 14), Color.black);
			DrawDisk(array, size, (int)((float)size * 0.66f), (int)((float)size * 0.42f), Mathf.Max(2, size / 14), Color.black);
			val.SetPixels(array);
			val.Apply();
			return val;
		}

		private static void DrawDisk(Color[] pixels, int size, int cx, int cy, int r, Color color)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			int num = size - 1 - cy;
			for (int i = -r; i <= r; i++)
			{
				for (int j = -r; j <= r; j++)
				{
					if (j * j + i * i <= r * r)
					{
						int num2 = cx + j;
						int num3 = num - i;
						if (num2 >= 0 && num2 < size && num3 >= 0 && num3 < size)
						{
							pixels[num3 * size + num2] = color;
						}
					}
				}
			}
		}
	}
	internal class MidRunJoinPopup : MonoBehaviour
	{
		private bool _isShowing;

		private bool _hasChoice;

		private bool _choiceIsSpectate;

		private float _shownAtUnscaledTime;

		private const float SelfHideTimeoutSeconds = 300f;

		private GUIStyle? _titleStyle;

		private GUIStyle? _bodyStyle;

		private GUIStyle? _buttonStyle;

		private GUIStyle? _boxStyle;

		private Texture2D? _backgroundTexture;

		private Texture2D? _scrimTexture;

		private const int PanelWidth = 520;

		private const int PanelHeight = 240;

		private const int ButtonHeight = 56;

		private const int ButtonGap = 16;

		private const int PaddingX = 32;

		private const int PaddingY = 28;

		internal static MidRunJoinPopup? Instance { get; private set; }

		internal bool IsShowing => _isShowing;

		internal bool HasChoice => _hasChoice;

		internal bool ChoiceIsSpectate => _choiceIsSpectate;

		private void Awake()
		{
			Instance = this;
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		internal void Show()
		{
			_hasChoice = false;
			_choiceIsSpectate = false;
			_isShowing = true;
			_shownAtUnscaledTime = Time.realtimeSinceStartup;
			Plugin.TraceDebug("[trace] MidRunJoinPopup shown");
		}

		private void Update()
		{
			if (_isShowing && Time.realtimeSinceStartup - _shownAtUnscaledTime > 300f)
			{
				Plugin.TraceWarn($"[trace] MidRunJoinPopup self-hiding after {300f:0}s timeout (waiter coroutine likely orphaned)");
				Hide();
			}
		}

		internal void Hide()
		{
			_isShowing = false;
			Plugin.TraceDebug("[trace] MidRunJoinPopup hidden.");
		}

		private void OnGUI()
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: 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_01d0: Unknown result type (might be due to invalid IL or missing references)
			if (_isShowing)
			{
				EnsureStyles();
				if (_boxStyle != null && (Object)(object)_backgroundTexture != (Object)null)
				{
					_boxStyle.normal.background = _backgroundTexture;
				}
				int depth = GUI.depth;
				GUI.depth = -1000;
				if ((Object)(object)_scrimTexture != (Object)null)
				{
					GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)_scrimTexture);
				}
				Rect val = default(Rect);
				((Rect)(ref val))..ctor((float)(Screen.width - 520) / 2f, (float)(Screen.height - 240) / 2f, 520f, 240f);
				GUI.Box(val, GUIContent.none, _boxStyle);
				float num = ((Rect)(ref val)).y + 28f;
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor(((Rect)(ref val)).x + 32f, num, ((Rect)(ref val)).width - 64f, 30f);
				GUI.Label(val2, "Join this run as…", _titleStyle);
				num += 38f;
				Rect val3 = default(Rect);
				((Rect)(ref val3))..ctor(((Rect)(ref val)).x + 32f, num, ((Rect)(ref val)).width - 64f, 50f);
				GUI.Label(val3, "Pick how to join. Live climbers play the run; spectators watch.", _bodyStyle);
				num += 58f;
				float num2 = num;
				float num3 = (((Rect)(ref val)).width - 64f - 16f) / 2f;
				Rect val4 = default(Rect);
				((Rect)(ref val4))..ctor(((Rect)(ref val)).x + 32f, num2, num3, 56f);
				Rect val5 = default(Rect);
				((Rect)(ref val5))..ctor(((Rect)(ref val)).x + 32f + num3 + 16f, num2, num3, 56f);
				if (GUI.Button(val4, "Play", _buttonStyle))
				{
					Commit(spectate: false);
				}
				if (GUI.Button(val5, "Spectate", _buttonStyle))
				{
					Commit(spectate: true);
				}
				GUI.depth = depth;
			}
		}

		private void Commit(bool spectate)
		{
			_choiceIsSpectate = spectate;
			_hasChoice = true;
			Plugin.SpectatorEnabled.Value = spectate;
			Plugin.Trace($"[trace] MidRunJoinPopup committed: spectate={spectate}");
		}

		private void EnsureStyles()
		{
			//IL_000e: 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_004d: 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)
			//IL_005a: 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_006d: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: 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_00ca: Expected O, but got Unknown
			if (_titleStyle == null)
			{
				_titleStyle = GuiHelpers.MakeLabelStyle(22, (FontStyle)1, (TextAnchor)3, Color.white);
				_bodyStyle = GuiHelpers.MakeLabelStyle(14, (FontStyle)0, (TextAnchor)0, new Color(0.88f, 0.88f, 0.92f), wordWrap: true);
				_buttonStyle = new GUIStyle(GUI.skin.button)
				{
					fontSize = 18,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4
				};
				_backgroundTexture = GuiHelpers.BuildSolidTexture(new Color(0.08f, 0.08f, 0.12f, 0.96f));
				_scrimTexture = GuiHelpers.BuildSolidTexture(new Color(0f, 0f, 0f, 0.65f));
				_boxStyle = new GUIStyle(GUI.skin.box);
				_boxStyle.normal.background = _backgroundTexture;
			}
		}
	}
	internal class RoomCallbackHandler : MonoBehaviourPunCallbacks
	{
		[CompilerGenerated]
		private sealed class <DeferredRecordRole>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Player targetPlayer;

			private int <i>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DeferredRecordRole>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<i>5__2 = 0;
					break;
				case 1:
					<>1__state = -1;
					if (targetPlayer == null)
					{
						return false;
					}
					if (!string.IsNullOrEmpty(targetPlayer.UserId))
					{
						if (PhotonNetwork.IsMasterClient)
						{
							RecordRoleIfNew(targetPlayer);
						}
						return false;
					}
					<i>5__2++;
					break;
				}
				if (<i>5__2 < 120)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				object arg = 120;
				Player obj = targetPlayer;
				Plugin.TraceWarn($"[trace] DeferredRecordRole gave up after {arg}f waiting for UserId on {((obj != null) ? obj.NickName : null)}");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string IsSpectatorKey = "GhostSpectator.IsSpectator";

		private EventHandler? _settingChangedHandler;

		private static Player[] _sortedPlayers = (Player[])(object)new Player[0];

		private static readonly string[] _emptyLabels = new string[0];

		private static string[] _sortedPlayerLabels = _emptyLabels;

		private static readonly Player[] _empty = (Player[])(object)new Player[0];

		public static Player[] SortedPlayers => _sortedPlayers;

		public static string[] SortedPlayerLabels => _sortedPlayerLabels;

		private void Awake()
		{
			if (PhotonNetwork.InRoom)
			{
				PublishSpectatorStatus();
			}
			_settingChangedHandler = delegate
			{
				PublishSpectatorStatus();
			};
			Plugin.SpectatorEnabled.SettingChanged += _settingChangedHandler;
		}

		private void OnDestroy()
		{
			if (_settingChangedHandler != null)
			{
				Plugin.SpectatorEnabled.SettingChanged -= _settingChangedHandler;
				_settingChangedHandler = null;
			}
		}

		public override void OnJoinedRoom()
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			Plugin.TraceDebug("[trace] OnJoinedRoom fired. RoomName=" + ((currentRoom != null) ? currentRoom.Name : null));
			SpectatorState.HasPublishedLocalCustomizationData = false;
			SpectatorState.KioskRefusedTimestamp = -1f;
			SpectatorState.KioskRefusedMessage = string.Empty;
			PublishSpectatorStatus();
			RebuildSortedPlayers();
			Room currentRoom2 = PhotonNetwork.CurrentRoom;
			if (((currentRoom2 != null) ? ((RoomInfo)currentRoom2).CustomProperties : null) != null && ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"GhostSpectator.RunRoles", out object value))
			{
				RoleLock.DeserializeRunRolesFromHashtable(value);
				Plugin.TraceDebug($"[trace] RunRoles seeded from room property at join: {RoleLock.RunRoles.Count} entries");
			}
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			Plugin.TraceDebug("[trace] OnPlayerEnteredRoom fired for " + ((newPlayer != null) ? newPlayer.NickName : null));
			PublishSpectatorStatus();
			RebuildSortedPlayers();
		}

		public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
		{
			//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)
			if (!PhotonNetwork.IsMasterClient || targetPlayer == null || changedProps == null || !((Dictionary<object, object>)(object)changedProps).ContainsKey((object)"GhostSpectator.IsSpectator"))
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (!(name == "Airport"))
			{
				if (string.IsNullOrEmpty(targetPlayer.UserId))
				{
					((MonoBehaviour)this).StartCoroutine(DeferredRecordRole(targetPlayer));
				}
				else
				{
					RecordRoleIfNew(targetPlayer);
				}
			}
		}

		[IteratorStateMachine(typeof(<DeferredRecordRole>d__7))]
		private IEnumerator DeferredRecordRole(Player targetPlayer)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DeferredRecordRole>d__7(0)
			{
				targetPlayer = targetPlayer
			};
		}

		private static void RecordRoleIfNew(Player targetPlayer)
		{
			if (string.IsNullOrEmpty(targetPlayer.UserId))
			{
				return;
			}
			bool flag = SpectatorState.ClaimsSpectator(targetPlayer);
			bool flag2 = false;
			lock (RoleLock.RunRoles)
			{
				if (!RoleLock.RunRoles.ContainsKey(targetPlayer.UserId))
				{
					RoleLock.RunRoles[targetPlayer.UserId] = flag;
					flag2 = true;
					Plugin.Trace($"[trace] recorded run role for {targetPlayer.NickName} ({targetPlayer.UserId}): isSpec={flag}");
				}
			}
			if (flag2)
			{
				RoleLock.PublishRunRolesToNetwork();
			}
		}

		public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
		{
			if (propertiesThatChanged == null || !((Dictionary<object, object>)(object)propertiesThatChanged).ContainsKey((object)"GhostSpectator.RunRoles"))
			{
				return;
			}
			if (PhotonNetwork.IsMasterClient)
			{
				object obj = propertiesThatChanged[(object)"GhostSpectator.RunRoles"];
				Hashtable inbound = (Hashtable)((obj is Hashtable) ? obj : null);
				if (!RoleLock.RunRolesHashtableMatchesInMemory(inbound))
				{
					Plugin.TraceWarn("[trace] non-master overwrote RunRoles room property; republishing authoritative value");
					RoleLock.PublishRunRolesToNetwork();
					return;
				}
			}
			RoleLock.DeserializeRunRolesFromHashtable(propertiesThatChanged[(object)"GhostSpectator.RunRoles"]);
			Plugin.TraceDebug($"[trace] RunRoles synced from room property: {RoleLock.RunRoles.Count} entries");
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			Plugin.TraceDebug("[trace] OnPlayerLeftRoom fired for " + ((otherPlayer != null) ? otherPlayer.NickName : null));
			RebuildSortedPlayers();
		}

		public override void OnLeftRoom()
		{
			_sortedPlayers = _empty;
			_sortedPlayerLabels = _emptyLabels;
		}

		public override void OnMasterClientSwitched(Player newMasterClient)
		{
			Plugin.TraceDebug($"[trace] OnMasterClientSwitched fired. newMaster=#{((newMasterClient != null) ? new int?(newMasterClient.ActorNumber) : null)}");
			SpectatorState.RunHasEnded = false;
			SpectatorState.BannersOverriddenThisRun = false;
			if (PhotonNetwork.IsMasterClient)
			{
				RoleLock.TryWriteRunRolesToSteamLobby();
			}
		}

		private static void RebuildSortedPlayers()
		{
			if (!PhotonNetwork.InRoom)
			{
				_sortedPlayers = _empty;
				_sortedPlayerLabels = _emptyLabels;
				return;
			}
			_sortedPlayers = PhotonNetwork.PlayerList.OrderBy((Player p) => p.ActorNumber).ToArray();
			_sortedPlayerLabels = new string[_sortedPlayers.Length];
			for (int i = 0; i < _sortedPlayers.Length; i++)
			{
				Player val = _sortedPlayers[i];
				string text = (string.IsNullOrEmpty((val != null) ? val.NickName : null) ? "(unnamed)" : val.NickName);
				_sortedPlayerLabels[i] = ((val != null && val.IsLocal) ? (text + " (you)") : text);
			}
		}

		public static void PublishSpectatorStatus()
		{
			//IL_0012: 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_0031: Expected O, but got Unknown
			//IL_0032: Expected O, but got Unknown
			if (!PhotonNetwork.InRoom)
			{
				Plugin.TraceDebug("[trace] PublishSpectatorStatus skipped, not InRoom");
				return;
			}
			Hashtable val = new Hashtable();
			((Dictionary<object, object>)val).Add((object)"GhostSpectator.IsSpectator", (object)Plugin.SpectatorEnabled.Value);
			Hashtable val2 = val;
			PhotonNetwork.LocalPlayer.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
			Plugin.TraceDebug($"[trace] published IsSpectator={Plugin.SpectatorEnabled.Value}");
		}

		public static int CountNonSpectators()
		{
			if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
			{
				return 0;
			}
			int num = 0;
			foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values)
			{
				if (!SpectatorState.ClaimsSpectator(value))
				{
					num++;
				}
			}
			return num;
		}

		public static Player[] GetNonSpectatorPlayerList()
		{
			if (!PhotonNetwork.InRoom)
			{
				return _empty;
			}
			List<Player> list = new List<Player>(PhotonNetwork.PlayerList.Length);
			Player[] playerList = PhotonNetwork.PlayerList;
			foreach (Player val in playerList)
			{
				if (val != null && !SpectatorState.IsTrustedSpectator(val) && (val.IsLocal || SpectatorState.HasGhostSpectatorMod(val)))
				{
					list.Add(val);
				}
			}
			return list.ToArray();
		}
	}
	internal class SpectatorMenuUI : MonoBehaviour
	{
		private GUIStyle? _titleStyle;

		private GUIStyle? _sectionHeaderStyle;

		private GUIStyle? _rowStyle;

		private GUIStyle? _boxStyle;

		private GUIStyle? _powerButtonStyle;

		private Texture2D? _ghostTexture;

		private Texture2D? _climberTexture;

		private Texture2D? _powerTexture;

		private Texture2D? _panelBackground;

		private const int PanelWidth = 240;

		private const int ScreenMargin = 20;

		private const int PanelPaddingX = 12;

		private const int PanelPaddingY = 10;

		private const int RowHeight = 24;

		private const int TitleHeight = 28;

		private const int SectionHeaderHeight = 20;

		private const int SectionGap = 4;

		private const int IconSize = 18;

		private const int IconPadding = 2;

		private const int PowerButtonSize = 26;

		private static readonly Color FallbackGhostColor = new Color(0.78f, 0.66f, 1f, 1f);

		private static readonly Color PowerOnColor = new Color(0.3f, 0.95f, 0.4f, 1f);

		private static readonly Color PowerOffColor = new Color(0.62f, 0.62f, 0.68f, 1f);

		private static readonly Color EmptySlotColor = new Color(0.6f, 0.6f, 0.65f, 0.45f);

		private GUIStyle? _kioskBannerStyle;

		private Texture2D? _kioskBannerBackground;

		private void OnGUI()
		{
			if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null)
			{
				EnsureTextures();
				EnsureStyles();
				if (_boxStyle != null && (Object)(object)_panelBackground != (Object)null)
				{
					_boxStyle.normal.background = _panelBackground;
				}
				DrawPlayerPanel(20f);
				DrawKioskRefusedMessage();
			}
		}

		private void DrawKioskRefusedMessage()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_006f: Expected O, but got Unknown
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			if (SpectatorState.KioskRefusedTimestamp < 0f)
			{
				return;
			}
			float num = Time.realtimeSinceStartup - SpectatorState.KioskRefusedTimestamp;
			if (!(num > 5f))
			{
				if (_kioskBannerStyle == null)
				{
					_kioskBannerStyle = new GUIStyle(GUI.skin.label)
					{
						fontSize = 18,
						fontStyle = (FontStyle)1,
						alignment = (TextAnchor)4,
						wordWrap = true,
						padding = new RectOffset(20, 20, 14, 14)
					};
					_kioskBannerStyle.normal.textColor = Color.white;
				}
				if ((Object)(object)_kioskBannerBackground == (Object)null)
				{
					_kioskBannerBackground = GuiHelpers.BuildSolidTexture(new Color(0.2f, 0.06f, 0.06f, 0.92f));
					_kioskBannerStyle.normal.background = _kioskBannerBackground;
				}
				else if ((Object)(object)_kioskBannerStyle.normal.background == (Object)null)
				{
					_kioskBannerStyle.normal.background = _kioskBannerBackground;
				}
				string text = (string.IsNullOrEmpty(SpectatorState.KioskRefusedMessage) ? "Can't start the run." : SpectatorState.KioskRefusedMessage);
				GUIContent val = new GUIContent(text);
				float num2 = _kioskBannerStyle.CalcHeight(val, 540f);
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor((float)(Screen.width - 540) / 2f, (float)Screen.height * 0.18f, 540f, num2);
				GUI.Label(val2, val, _kioskBannerStyle);
			}
		}

		private void DrawPlayerPanel(float topY)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: 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_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0338: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			bool flag = ((Scene)(ref activeScene)).name == "Airport";
			Player[] sortedPlayers = RoomCallbackHandler.SortedPlayers;
			string[] sortedPlayerLabels = RoomCallbackHandler.SortedPlayerLabels;
			int num = 0;
			int num2 = 0;
			foreach (Player val in sortedPlayers)
			{
				if (val != null)
				{
					if (SpectatorState.ClaimsSpectator(val))
					{
						num2++;
					}
					else
					{
						num++;
					}
				}
			}
			int num3 = 4;
			int num4 = num2;
			int num5 = 68 + num3 * 24 + 4 + 20 + num4 * 24;
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(20f, topY, 240f, (float)num5);
			GUI.Box(val2, GUIContent.none, _boxStyle);
			float num6 = ((Rect)(ref val2)).y + 10f;
			Rect val3 = default(Rect);
			((Rect)(ref val3))..ctor(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 28f);
			Rect val4 = (Rect)(flag ? new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y, ((Rect)(ref val3)).width - 26f - 6f, ((Rect)(ref val3)).height) : val3);
			GUI.Label(val4, "Ghost Spectator", _titleStyle);
			if (flag)
			{
				Rect rect = default(Rect);
				((Rect)(ref rect))..ctor(((Rect)(ref val3)).xMax - 26f, ((Rect)(ref val3)).y + 1f, 26f, 26f);
				DrawPowerToggle(rect);
			}
			num6 += 28f;
			DrawSectionHeader(val2, num6, $"LIVE {num}/{4}");
			num6 += 20f;
			int j = 0;
			for (int k = 0; k < sortedPlayers.Length; k++)
			{
				if (j >= num3)
				{
					break;
				}
				Player val5 = sortedPlayers[k];
				if (val5 != null && !SpectatorState.ClaimsSpectator(val5))
				{
					string label = ((k < sortedPlayerLabels.Length) ? sortedPlayerLabels[k] : (string.IsNullOrEmpty(val5.NickName) ? "(unnamed)" : val5.NickName));
					Texture2D icon = (flag ? _climberTexture : (SpectatorState.IsPlayerCharacterDead(val5) ? _ghostTexture : _climberTexture));
					DrawPlayerRow(val5, label, new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f), icon);
					num6 += 24f;
					j++;
				}
			}
			for (; j < num3; j++)
			{
				DrawEmptySlot(new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f));
				num6 += 24f;
			}
			num6 += 4f;
			DrawSectionHeader(val2, num6, $"SPECTATORS {num2}/{16}");
			num6 += 20f;
			for (int l = 0; l < sortedPlayers.Length; l++)
			{
				Player val6 = sortedPlayers[l];
				if (val6 != null && SpectatorState.ClaimsSpectator(val6))
				{
					string label2 = ((l < sortedPlayerLabels.Length) ? sortedPlayerLabels[l] : (string.IsNullOrEmpty(val6.NickName) ? "(unnamed)" : val6.NickName));
					DrawPlayerRow(val6, label2, new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f), _ghostTexture);
					num6 += 24f;
				}
			}
		}

		private void DrawSectionHeader(Rect panelRect, float y, string text)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((Rect)(ref panelRect)).x + 12f, y, ((Rect)(ref panelRect)).width - 24f, 20f);
			GUI.Label(val, text, _sectionHeaderStyle);
		}

		private void DrawEmptySlot(Rect row)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			Color color = GUI.color;
			GUI.color = EmptySlotColor;
			GUI.Label(row, "(open)", _rowStyle);
			GUI.color = color;
		}

		private void DrawPowerToggle(Rect rect)
		{
			//IL_0015: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			bool value = Plugin.SpectatorEnabled.Value;
			Color color = (value ? PowerOnColor : PowerOffColor);
			if (GUI.Button(rect, GUIContent.none, _powerButtonStyle))
			{
				Plugin.SpectatorEnabled.Value = !value;
				Plugin.Trace($"GhostSpectator: toggled via menu to {Plugin.SpectatorEnabled.Value}");
			}
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((Rect)(ref rect)).x + 4f, ((Rect)(ref rect)).y + 4f, ((Rect)(ref rect)).width - 8f, ((Rect)(ref rect)).height - 8f);
			Color color2 = GUI.color;
			GUI.color = color;
			if ((Object)(object)_powerTexture != (Object)null)
			{
				GUI.DrawTexture(val, (Texture)(object)_powerTexture, (ScaleMode)2);
			}
			GUI.color = color2;
		}

		private void DrawPlayerRow(Player player, string label, Rect row, Texture2D? icon)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			float num = (((Object)(object)icon != (Object)null) ? (((Rect)(ref row)).width - 18f - 2f) : ((Rect)(ref row)).width);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((Rect)(ref row)).x, ((Rect)(ref row)).y, num, ((Rect)(ref row)).height);
			GUI.Label(val, label, _rowStyle);
			if (!((Object)(object)icon == (Object)null))
			{
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor(((Rect)(ref row)).xMax - 18f, ((Rect)(ref row)).y + (((Rect)(ref row)).height - 18f) / 2f, 18f, 18f);
				Color color = GUI.color;
				GUI.color = (Color)(((??)GetPlayerSkinColor(player)) ?? FallbackGhostColor);
				GUI.DrawTexture(val2, (Texture)(object)icon);
				GUI.color = color;
			}
		}

		private static Color? GetPlayerSkinColor(Player player)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PersistentPlayerDataService service = GameHandler.GetService<PersistentPlayerDataService>();
				if (service == null)
				{
					return null;
				}
				PersistentPlayerData playerData = service.GetPlayerData(player);
				if (playerData?.customizationData == null)
				{
					return null;
				}
				Customization instance = Singleton<Customization>.Instance;
				if ((Object)(object)instance == (Object)null || instance.skins == null || instance.skins.Length == 0)
				{
					return null;
				}
				int currentSkin = playerData.customizationData.currentSkin;
				if (currentSkin < 0 || currentSkin >= instance.skins.Length)
				{
					return null;
				}
				CustomizationOption val = instance.skins[currentSkin];
				return ((Object)(object)val != (Object)null) ? new Color?(val.color) : null;
			}
			catch (NullReferenceException)
			{
				return null;
			}
			catch (IndexOutOfRangeException)
			{
				return null;
			}
		}

		private void EnsureStyles()
		{
			//IL_000e: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_0077: 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)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			//IL_0090: Expected O, but got Unknown
			if (_titleStyle == null)
			{
				_titleStyle = GuiHelpers.MakeLabelStyle(18, (FontStyle)1, (TextAnchor)3, Color.white);
				_sectionHeaderStyle = GuiHelpers.MakeLabelStyle(11, (FontStyle)1, (TextAnchor)3, new Color(0.72f, 0.74f, 0.8f));
				_rowStyle = GuiHelpers.MakeLabelStyle(14, (FontStyle)0, (TextAnchor)3, Color.white);
				_boxStyle = new GUIStyle(GUI.skin.box);
				_powerButtonStyle = new GUIStyle(GUI.skin.button)
				{
					padding = new RectOffset(2, 2, 2, 2)
				};
			}
		}

		private void EnsureTextures()
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_ghostTexture == (Object)null)
			{
				_ghostTexture = GuiHelpers.BuildGhostTexture(40);
			}
			if ((Object)(object)_climberTexture == (Object)null)
			{
				_climberTexture = BuildClimberTexture(40);
			}
			if ((Object)(object)_powerTexture == (Object)null)
			{
				_powerTexture = BuildPowerTexture(40);
			}
			if ((Object)(object)_panelBackground == (Object)null)
			{
				_panelBackground = GuiHelpers.BuildSolidTexture(new Color(0.06f, 0.06f, 0.09f, 0.45f));
			}
		}

		private static Texture2D BuildClimberTexture(int size)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1,
				hideFlags = (HideFlags)61
			};
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = Color.clear;
			}
			float num = (float)size / 2f;
			float num2 = (float)size * 0.22f;
			float num3 = (float)size * 0.16f;
			float num4 = (float)size * 0.4f;
			float num5 = (float)size * 0.92f;
			float num6 = (float)size * 0.34f;
			float num7 = (float)size * 0.24f;
			for (int j = 0; j < size; j++)
			{
				float num8 = (float)j + 0.5f;
				int num9 = size - 1 - j;
				for (int k = 0; k < size; k++)
				{
					float num10 = (float)k + 0.5f;
					float num11 = num10 - num;
					bool flag = false;
					float num12 = num8 - num2;
					if (num11 * num11 + num12 * num12 <= num3 * num3)
					{
						flag = true;
					}
					else if (num8 >= num4 && num8 <= num5)
					{
						float num13 = (num8 - num4) / (num5 - num4);
						float num14 = Mathf.Lerp(num6, num7, num13);
						if (Mathf.Abs(num11) <= num14)
						{
							flag = true;
						}
					}
					if (flag)
					{
						array[num9 * size + k] = Color.white;
					}
				}
			}
			val.SetPixels(array);
			val.Apply();
			return val;
		}

		private static Texture2D BuildPowerTexture(int size)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1,
				hideFlags = (HideFlags)61
			};
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = Color.clear;
			}
			float num = (float)size / 2f;
			float num2 = (float)size / 2f;
			float num3 = (float)size * 0.42f;
			float num4 = (float)size * 0.3f;
			float num5 = (float)size * 0.07f;
			float num6 = (float)size * 0.1f;
			float num7 = (float)size * 0.52f;
			float num8 = MathF.PI / 10f;
			for (int j = 0; j < size; j++)
			{
				float num9 = (float)j + 0.5f;
				int num10 = size - 1 - j;
				float num11 = num9 - num2;
				for (int k = 0; k < size; k++)
				{
					float num12 = (float)k + 0.5f;
					float num13 = num12 - num;
					bool flag = false;
					float num14 = num13 * num13 + num11 * num11;
					if (num14 >= num4 * num4 && num14 <= num3 * num3)
					{
						bool flag2 = false;
						if (num11 < 0f)
						{
							float num15 = Mathf.Atan2(Mathf.Abs(num13), 0f - num11);
							if (num15 < num8)
							{
								flag2 = true;
							}
						}
						if (!flag2)
						{
							flag = true;
						}
					}
					if (num9 >= num6 && num9 <= num7 && Mathf.Abs(num13) <= num5)
					{
						flag = true;
					}
					if (flag)
					{
						array[num10 * size + k] = Color.white;
					}
				}
			}
			val.SetPixels(array);
			val.Apply();
			return val;
		}
	}
}
namespace GhostSpectator.Patches
{
	[HarmonyPatch(typeof(MainCameraMovement), "Spectate")]
	internal static class Patch_MainCameraMovement_Spectate
	{
		private static int _fallbackLogCount;

		[HarmonyPostfix]
		private static void Postfix(MainCameraMovement __instance)
		{
			//IL_000d: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: 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)
			//IL_004a: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: 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_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: 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)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.SpectatorEnabled.Value)
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name == "Airport")
			{
				return;
			}
			Character specCharacter = MainCameraMovement.specCharacter;
			if ((Object)(object)specCharacter != (Object)null && !SpectatorState.IsTrustedSpectator(specCharacter))
			{
				SpectatorState.LastSpectateCameraPosition = ((Component)__instance).transform.position;
				SpectatorState.LastSpectateCameraRotation = ((Component)__instance).transform.rotation;
				SpectatorState.LastSpectateCameraValid = true;
				return;
			}
			if (SpectatorState.LastSpectateCameraValid)
			{
				((Component)__instance).transform.position = SpectatorState.LastSpectateCameraPosition;
				((Component)__instance).transform.rotation = SpectatorState.LastSpectateCameraRotation;
				return;
			}
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)localCharacter == (Object)null) && SpectatorState.SpectatorSpawnPositionValid)
			{
				Vector3 spectatorSpawnPosition = SpectatorState.SpectatorSpawnPosition;
				Vector3 val = -localCharacter.data.lookDirection;
				Vector3 val2 = spectatorSpawnPosition + val * 3f + Vector3.up * 2f;
				Vector3 val3 = spectatorSpawnPosition + Vector3.up * 1.5f;
				((Component)__instance).transform.position = val2;
				Transform transform = ((Component)__instance).transform;
				Vector3 val4 = val3 - val2;
				transform.rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized);
				if (_fallbackLogCount < 3)
				{
					Plugin.Trace($"[trace] camera spawn-anchor fallback active (no live target ever this run). anchor={spectatorSpawnPosition}, lookTarget={val3}, camPos={val2}");
					_fallbackLogCount++;
				}
			}
		}
	}
	[HarmonyPatch(typeof(PersistentPlayerDataService), "OnSyncReceived")]
	internal static class Patch_PersistentPlayerDataService_OnSyncReceived
	{
		[HarmonyPrefix]
		private static bool Prefix(SyncPersistentPlayerDataPackage package)
		{
			if (!PhotonNetwork.InRoom)
			{
				return true;
			}
			Player localPlayer = PhotonNetwork.LocalPlayer;
			if (localPlayer == null)
			{
				return true;
			}
			if (package.ActorNumber != localPlayer.ActorNumber)
			{
				return true;
			}
			if (!SpectatorState.HasPublishedLocalCustomizationData)
			{
				return true;
			}
			Plugin.Trace($"[trace] ignored echoed self-sync (actor #{package.ActorNumber}); local is authoritative.");
			return false;
		}
	}
	[HarmonyPatch(typeof(PersistentPlayerDataService), "SetPlayerData")]
	internal static class Patch_PersistentPlayerDataService_SetPlayerData
	{
		[HarmonyPostfix]
		private static void Postfix(Player player)
		{
			if (player != null && PhotonNetwork.InRoom)
			{
				Player localPlayer = PhotonNetwork.LocalPlayer;
				if (localPlayer != null && player.ActorNumber == localPlayer.ActorNumber)
				{
					SpectatorState.HasPublishedLocalCustomizationData = true;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Start")]
	internal static class Patch_Character_Start
	{
		[HarmonyPostfix]
		private static void Postfix(Character __instance)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0093: 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)
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			bool flag = (Object)(object)Character.localCharacter != (Object)null && (Object)(object)__instance == (Object)(object)Character.localCharacter;
			Plugin.TraceDebug($"[trace] Character.Start postfix fired. scene={name}, isLocal={flag}, SpectatorEnabled={Plugin.SpectatorEnabled.Value}");
			if (flag)
			{
				SpectatorState.SpectatorSpawnPositionValid = false;
				SpectatorState.LastSpectateCameraValid = false;
				SpectatorState.RunHasEnded = false;
				SpectatorState.BannersOverriddenThisRun = false;
			}
			if (SpectatorState.IsLocalSpectator(__instance))
			{
				activeScene = SceneManager.GetActiveScene();
				if (!(((Scene)(ref activeScene)).name == "Airport"))
				{
					SpectatorState.SpectatorSpawnPosition = ((Component)__instance).transform.position;
					SpectatorState.SpectatorSpawnPositionValid = true;
					((MonoBehaviourPun)__instance).photonView.RPC("RPCA_SetDead", (RpcTarget)0, Array.Empty<object>());
					__instance.refs.ragdoll.ToggleCollision(false);
					Plugin.Trace("GhostSpectator: local character forced into ghost state at spawn (RPCA_SetDead broadcast).");
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterData), "RPC_SyncOnJoin")]
	internal static class Patch_CharacterData_RPC_SyncOnJoin
	{
		[HarmonyPostfix]
		private static void Postfix(CharacterData __instance)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (SpectatorState.IsLocalSpectator(__instance.character))
			{
				Scene activeScene = SceneManager.GetActiveScene();
				if (!(((Scene)(ref activeScene)).name == "Airport"))
				{
					__instance.fullyPassedOut = true;
					__instance.dead = true;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "RPCA_Die")]
	internal static class Patch_Character_RPCA_Die
	{
		[HarmonyPrefix]
		private static bool Prefix(Character __instance)
		{
			if (SpectatorState.IsTrustedSpectator(__instance))
			{
				Plugin.TraceDebug("[trace] blocked RPCA_Die on spectator (already dead via RPCA_SetDead, no skeleton/items needed).");
				return false;
			}
			if (SpectatorState.TryGetOwner(__instance, out Player owner) && SpectatorState.ClaimsSpectator(owner))
			{
				Plugin.TraceDebug("[trace] blocked RPCA_Die on owner-claims-spectator (pre-corroboration race).");
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Character), "EndGame")]
	internal static class Patch_Character_EndGame
	{
		private static float? _firstSuppressUnscaledTime;

		private const float EndGameSuppressionTimeoutSeconds = 10f;

		[HarmonyPrefix]
		private static bool Prefix()
		{
			if (SpectatorState.RunHasEnded)
			{
				Plugin.Trace("[trace] EndGame called again in same run, suppressing duplicate to keep EndScreen's Next button intact.");
				return false;
			}
			if (!SpectatorState.AllExpectedCharactersSpawned(out var expectedPlayers, out var actualCharacters))
			{
				if (!_firstSuppressUnscaledTime.HasValue)
				{
					_firstSuppressUnscaledTime = Time.realtimeSinceStartup;
				}
				float num = Time.realtimeSinceStartup - _firstSuppressUnscaledTime.Value;
				if (num < 10f)
				{
					Plugin.Trace($"[trace] EndGame suppressed: {actualCharacters} of {expectedPlayers} Characters spawned, waiting for sync (suppressed {num:0.0}s).");
					return false;
				}
				Plugin.TraceWarn($"[trace] EndGame suppression timeout ({num:0.0}s); allowing EndGame despite {expectedPlayers - actualCharacters} missing Character(s).");
			}
			_firstSuppressUnscaledTime = null;
			SpectatorState.RunHasEnded = true;
			Plugin.Trace("[trace] EndGame firing for the first time this run.");
			return true;
		}

		internal static void ResetSuppressionTimer()
		{
			_firstSuppressUnscaledTime = null;
		}
	}
	[HarmonyPatch(typeof(OrbFogHandler), "PlayersHaveMovedOn")]
	internal static class Patch_OrbFogHandler_PlayersHaveMovedOn
	{
		[HarmonyPrefix]
		private static bool Prefix(ref bool __result)
		{
			if (!SpectatorState.AllExpectedCharactersSpawned(out var _, out var _))
			{
				__result = false;
				return false;
			}
			bool flag = false;
			if (Character.AllCharacters != null)
			{
				for (int i = 0; i < Character.AllCharacters.Count; i++)
				{
					Character val = Character.AllCharacters[i];
					if ((Object)(object)val != (Object)null && !SpectatorState.IsTrustedSpectator(val))
					{
						flag = true;
						break;
					}
				}
			}
			if (!flag)
			{
				__result = false;
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class Patch_EndScreen_EndSequenceRoutine_Banners
	{
		private static readonly string[] DeadBannerMessages = new string[3] { "NO BODIES.<br><size=125%>NOT EVEN YOURS", "A WITNESS<br><size=125%>TO NOTHING", "THE MOUNTAIN<br><size=125%>KEPT THEM ALL" };

		private static readonly string[] FriendsWonBannerMessages = new string[3] { "GLORY BY<br><size=125%>PROXY", "YOU HAUNTED THEM<br><size=125%>TO THE TOP", "THEY SUMMITED<br><size=125%>YOU WITNESSED" };

		private static MethodBase TargetMethod()
		{
			return SpectatorState.GetCoroutineMoveNext(typeof(EndScreen), "<EndSequenceRoutine>", "Patch_EndScreen_EndSequenceRoutine_Banners");
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			if (SpectatorState.BannersOverriddenThisRun || !Plugin.SpectatorEnabled.Value)
			{
				return;
			}
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || !SpectatorState.IsLocalSpectator(localCharacter))
			{
				return;
			}
			EndScreen instance = EndScreen.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			GameObject val = null;
			string[] array = null;
			if ((Object)(object)instance.deadBanner != (Object)null && instance.deadBanner.activeSelf)
			{
				val = instance.deadBanner;
				array = DeadBannerMessages;
			}
			else if ((Object)(object)instance.yourFriendsWonBanner != (Object)null && instance.yourFriendsWonBanner.activeSelf)
			{
				val = instance.yourFriendsWonBanner;
				array = FriendsWonBannerMessages;
			}
			if ((Object)(object)val == (Object)null || array == null)
			{
				return;
			}
			string text = array[Random.Range(0, array.Length)];
			LocalizedText componentInChildren = val.GetComponentInChildren<LocalizedText>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.SetText(text);
				SpectatorState.BannersOverriddenThisRun = true;
				Plugin.TraceDebug("[trace] EndScreen banner overridden via LocalizedText: \"" + text + "\"");
				return;
			}
			TMP_Text componentInChildren2 = val.GetComponentInChildren<TMP_Text>(true);
			if (!((Object)(object)componentInChildren2 == (Object)null))
			{
				componentInChildren2.text = text;
				SpectatorState.BannersOverriddenThisRun = true;
				Plugin.TraceDebug("[trace] EndScreen banner overridden via direct TMP_Text (no LocalizedText found): \"" + text + "\"");
			}
		}
	}
	[HarmonyPatch(typeof(WaitingForPlayersUI), "Update")]
	internal static class Patch_WaitingForPlayersUI_Update
	{
		private static Sprite? _vanillaScoutSprite;

		private static Sprite? _ghostSprite;

		[HarmonyPrefix]
		private static void Prefix(WaitingForPlayersUI __instance)
		{
			if (__instance.scoutImages == null || __instance.scoutImages.Length == 0 || __instance.scoutImages.Length >= Character.AllCharacters.Count)
			{
				return;
			}
			Image val = __instance.scoutImages[0];
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Image[] array = (Image[])(object)new Image[Character.AllCharacters.Count];
			for (int i = 0; i < Character.AllCharacters.Count; i++)
			{
				if (i < __instance.scoutImages.Length)
				{
					array[i] = __instance.scoutImages[i];
				}
				else
				{
					array[i] = Object.Instantiate<Image>(val, ((Component)val).transform.parent);
				}
			}
			__instance.scoutImages = array;
		}

		[HarmonyPostfix]
		private static void Postfix(WaitingForPlayersUI __instance)
		{
			//IL_0099: 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 (__instance.scoutImages == null || __instance.scoutImages.Length == 0)
			{
				return;
			}
			if ((Object)(object)_vanillaScoutSprite == (Object)null)
			{
				for (int i = 0; i < __instance.scoutImages.Length; i++)
				{
					if ((Object)(object)__instance.scoutImages[i] != (Object)null && (Object)(object)__instance.scoutImages[i].sprite != (Object)null)
					{
						_vanillaScoutSprite = __instance.scoutImages[i].sprite;
						break;
					}
				}
			}
			if ((Object)(object)_ghostSprite == (Object)null)
			{
				Texture2D val = GuiHelpers.BuildGhostTexture(64);
				_ghostSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
			}
			int num = 0;
			foreach (Player allPlayer in PlayerHandler.GetAllPlayers())
			{
				if (num >= __instance.scoutImages.Length)
				{
					break;
				}
				Image val2 = __instance.scoutImages[num];
				if ((Object)(object)val2 != (Object)null && (Object)(object)allPlayer != (Object)null && (Object)(object)((MonoBehaviourPun)allPlayer).photonView != (Object)null && ((MonoBehaviourPun)allPlayer).photonView.Owner != null)
				{
					Sprite val3 = (SpectatorState.IsTrustedSpectator(((MonoBehaviourPun)allPlayer).photonView.Owner) ? _ghostSprite : _vanillaScoutSprite);
					if ((Object)(object)val3 != (Object)null && (Object)(object)val2.sprite != (Object)(object)val3)
					{
						val2.sprite = val3;
					}
				}
				num++;
			}
		}
	}
	[HarmonyPatch(typeof(PointPinger), "DoPing")]
	internal static class Patch_PointPinger_DoPing
	{
		[HarmonyPrefix]
		private static void Prefix(PointPinger __instance)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_003f: Expected O, but got Unknown
			if (SpectatorState.IsGhost(__instance.character))
			{
				Camera main = Camera.main;
				if (!((Object)(object)main == (Object)null))
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"GhostSpec.PingerCamera", (object)((Component)main).transform.position);
					Hashtable val2 = val;
					PhotonNetwork.LocalPlayer.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
				}
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class Patch_PointPinger_get_canPing
	{
		[HarmonyPrefix]
		private static bool Prefix(PointPinger __instance, ref bool __result)
		{
			if (!SpectatorState.IsGhost(__instance.character))
			{
				return true;
			}
			__result = Time.time - __instance._timeLastPinged >= __instance.coolDown;
			return false;
		}
	}
	[HarmonyPatch(typeof(PointPinger), "ReceivePoint_Rpc")]
	internal static class Patch_PointPinger_ReceivePoint_Rpc
	{
		[HarmonyPrefix]
		private static bool Prefix(PointPinger __instance, Vector3 point, Vector3 hitNormal)
		{
			//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_002b: 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_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: 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)
			//IL_0084: 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_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: 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_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: 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_0138: 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_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: 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_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: 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_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: 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_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: 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_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance.character == (Object)null)
			{
				return true;
			}
			if (!SpectatorState.IsFiniteVec(point) || !SpectatorState.IsFiniteVec(hitNormal))
			{
				Plugin.TraceWarn($"GhostSpectator: dropped ReceivePoint_Rpc with non-finite payload (point={point}, hitNormal={hitNormal}).");
				return false;
			}
			Character localCharacter = Character.localCharacter;
			bool flag = SpectatorState.IsGhost(__instance.character);
			bool flag2 = SpectatorState.IsGhost(localCharacter);
			if (!flag && !flag2)
			{
				return true;
			}
			Camera main = Camera.main;
			Vector3 val;
			if (flag)
			{
				if ((Object)(object)__instance.character == (Object)(object)localCharacter && (Object)(object)main != (Object)null)
				{
					val = ((Component)main).transform.position;
				}
				else
				{
					val = __instance.character.Head;
					if (SpectatorState.TryGetOwner(__instance.character, out Player owner) && ((Dictionary<object, object>)(object)owner.CustomProperties).TryGetValue((object)"GhostSpec.PingerCamera", out object value) && value is Vector3 val2)
					{
						val = val2;
					}
				}
			}
			else
			{
				val = __instance.character.Head;
			}
			Vector3 val3 = ((flag2 && (Object)(object)main != (Object)null) ? ((Component)main).transform.position : (((Object)(object)localCharacter != (Object)null) ? localCharacter.Head : __instance.character.Head));
			if (!SpectatorState.IsFiniteVec(val) || !SpectatorState.IsFiniteVec(val3))
			{
				Plugin.TraceWarn($"GhostSpectator: dropped ReceivePoint_Rpc with non-finite head position (pingerHead={val}, receiverHead={val3}).");
				return false;
			}
			Vector3 val4 = point - val;
			if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f)
			{
				return false;
			}
			bool flag3 = Physics.Linecast(val, val3, LayerMask.op_Implicit(HelperFunctions.terrainMapMask));
			float num = Vector3.Distance(val, val3);
			if ((Object)(object)__instance.pointPrefab == (Object)null)
			{
				return false;
			}
			PointPing component = __instance.pointPrefab.GetComponent<PointPing>();
			if ((Object)(object)component == (Object)null)
			{
				return false;
			}
			Vector2 visibilityFullNoneNoLos = component.visibilityFullNoneNoLos;
			float num2 = 1f - Mathf.InverseLerp(visibilityFullNoneNoLos.x, visibilityFullNoneNoLos.x + (visibilityFullNoneNoLos.y - visibilityFullNoneNoLos.x) * (flag3 ? component.NoLosVisibilityMul : 1f), num);
			if (num2 <= 0f)
			{
				return false;
			}
			if ((Object)(object)__instance.pingInstance != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)__instance.pingInstance);
			}
			GameObject pointPrefab = __instance.pointPrefab;
			val4 = point - val;
			__instance.pingInstance = Object.Instantiate<GameObject>(pointPrefab, point, Quaternion.LookRotation(((Vector3)(ref val4)).normalized, Vector3.up));
			PointPing component2 = __instance.pingInstance.GetComponent<PointPing>();
			component2.hitNormal = hitNormal;
			component2.Init(__instance.character);
			component2.pointPinger = __instance;
			if (__instance.character.refs != null && (Object)(object)__instance.character.refs.mainRenderer != (Object)null)
			{
				((Renderer)component2.renderer).material = Object.Instantiate<Material>(((Renderer)__instance.character.refs.mainRenderer).sharedMaterial);
			}
			component2.material.SetFloat("_Opacity", num2);
			Object.Destroy((Object)(object)__instance.pingInstance, 2f);
			return false;
		}
	}
	[HarmonyPatch(typeof(GUIManager), "UpdateDyingBar")]
	internal static class Patch_GUIManager_UpdateDyingBar
	{
		private static GUIManager? _lastInstance;

		private static bool _lastObservedVisible;

		private static void SetObservedHudVisible(GUIManager m, bool visible)
		{
			if (m == _lastInstance && _lastObservedVisible == visible)
			{
				return;
			}
			_lastInstance = m;
			_lastObservedVisible = visible;
			float alpha = (visible ? 1f : 0f);
			if ((Object)(object)m.staminaCanvasGroup != (Object)null)
			{
				m.staminaCanvasGroup.alpha = alpha;
			}
			if ((Object)(object)m.mushroomsCanvasGroup != (Object)null)
			{
				m.mushroomsCanvasGroup.alpha = alpha;
			}
			if (m.items != null)
			{
				for (int i = 0; i < m.items.Length; i++)
				{
					if ((Object)(object)m.items[i] != (Object)null)
					{
						((Component)m.items[i]).gameObject.SetActive(visible);
					}
				}
			}
			if ((Object)(object)m.backpack != (Object)null)
			{
				((Component)m.backpack).gameObject.SetActive(visible);
			}
		}

		[HarmonyPrefix]
		private static bool Prefix(GUIManager __instance)
		{
			//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)
			if (!Plugin.SpectatorEnabled.Value)
			{
				return true;
			}
			if ((Object)(object)Character.localCharacter == (Object)null)
			{
				return true;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name == "Airport")
			{
				return true;
			}
			if ((Object)(object)__instance.dyingBarObject != (Object)null && __instance.dyingBarObject.activeSelf)
			{
				__instance.dyingBarObject.SetActive(false);
			}
			Character observedCharacter = Character.observedCharacter;
			bool flag = (Object)(object)observedCharacter == (Object)null || (Object)(object)observedCharacter == (Object)(object)Character.localCharacter;
			bool flag2 = (Object)(object)observedCharacter != (Object)null && !flag && SpectatorState.IsTrustedSpectator(observedCharacter);
			SetObservedHudVisible(__instance, !(flag || flag2));
			return false;
		}
	}
	[HarmonyPatch(typeof(CharacterAfflictions), "AddStatus")]
	internal static class Patch_CharacterAfflictions_AddStatus
	{
		[HarmonyPrefix]
		private static bool Prefix(CharacterAfflictions __instance, ref bool __result)
		{
			if (SpectatorState.IsLocalSpectator(__instance.character))
			{
				__result = false;
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(CharacterStats), "GetFinalTimelineInfo")]
	internal static class Patch_CharacterStats_GetFinalTimelineInfo
	{
		[HarmonyPrefix]
		private static bool Prefix(CharacterStats __instance, ref TimelineInfo __result)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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)
			if (__instance.timelineInfo != null && __instance.timelineInfo.Count > 0)
			{
				return true;
			}
			if (SpectatorState.TryBorrowTimelineInfo(first: false, out var result))
			{
				__result = result;
				return false;
			}
			__result = default(TimelineInfo);
			return false;
		}
	}
	[HarmonyPatch(typeof(CharacterStats), "GetFirstTimelineInfo")]
	internal static class Patch_CharacterStats_GetFirstTimelineInfo
	{
		[HarmonyPrefix]
		private static bool Prefix(CharacterStats __instance, ref TimelineInfo __result)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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)
			if (__instance.timelineInfo != null && __instance.timelineInfo.Count > 0)
			{
				return true;
			}
			if (SpectatorState.TryBorrowTimelineInfo(first: true, out var result))
			{
				__result = result;
				return false;
			}
			__result = default(TimelineInfo);
			return false;
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class Patch_NetworkingUtilities_MAX_PLAYERS
	{
		[HarmonyPrefix]
		private static bool Prefix(ref int __result)
		{
			__result = 20;
			return false;
		}
	}
	internal static class MidRunJoinGate
	{
		internal static string? PendingSceneName;

		internal static bool? LockedRoleForJoiner;

		internal static int LiveCountAtJoin = -1;
	}
	[HarmonyPatch(typeof(SteamLobbyHandler), "HandleMessage")]
	internal static class Patch_SteamLobbyHandler_HandleMessage_CaptureScene
	{
		[HarmonyPrefix]
		private static void Prefix(MessageType messageType, CSteamID lobbyID)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			if ((int)messageType != 2)
			{
				return;
			}
			string text;
			try
			{
				text = SteamMatchmaking.GetLobbyData(lobbyID, "CurrentScene");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[trace] reading Steam lobby CurrentScene threw: " + ex.GetType().Name + ": " + ex.Message));
				return;
			}
			if (string.IsNullOrEmpty(text))
			{
				text = "Airport";
			}
			MidRunJoinGate.PendingSceneName = text;
			Plugin.TraceDebug("[trace] HandleMessage RoomID: captured target scene '" + text + "' for upcoming Load");
			MidRunJoinGate.LockedRoleForJoiner = null;
			MidRunJoinGate.LiveCountAtJoin = -1;
			if (text == "Airport")
			{
				return;
			}
			try
			{
				string lobbyData = SteamMatchmaking.GetLobbyData(lobbyID, "GhostSpectator.RunRoles");
				if (string.IsNullOrEmpty(lobbyData))
				{
					return;
				}
				Dictionary<string, bool> dictionary = new Dictionary<string, bool>();
				RoleLock.DeserializeRunRolesFromString(lobbyData, dictionary);
				string text2 = SteamUser.GetSteamID().m_SteamID.ToString();
				if (dictionary.TryGetValue(text2, out var value))
				{
					MidRunJoinGate.LockedRoleForJoiner = value;
					Plugin.Trace($"[trace] HandleMessage RoomID: rejoiner detected (userId={text2}); locked role isSpec={value}");
				}
				int num = 0;
				foreach (KeyValuePair<string, bool> item in dictionary)
				{
					if (!item.Value)
					{
						num++;
					}
				}
				MidRunJoinGate.LiveCountAtJoin = num;
				Plugin.TraceDebug($"[trace] HandleMessage RoomID: counted {num} live player(s) already in run");
			}
			catch (Exception ex2)
			{
				Plugin.TraceDebug("[trace] reading Steam lobby RunRoles failed: " + ex2.GetType().Name + ": " + ex2.Message);
			}
		}
	}
	[HarmonyPatch(typeof(LoadingScreenHandler), "Load")]
	internal static class Patch_LoadingScreenHandler_Load_InjectPopupWait
	{
		[CompilerGenerated]
		private sealed class <WaitForMidRunChoice>d__1 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private MidRunJoinPopup <popup>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WaitForMidRunChoice>d__1(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<popup>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					bool? lockedRoleForJoiner = MidRunJoinGate.LockedRoleForJoiner;
					MidRunJoinGate.LockedRoleForJoiner = null;
					if (lockedRoleForJoiner.HasValue)
					{
						Plugin.Trace($"[trace] mid-run rejoiner: applying locked role spectate={lockedRoleForJoiner.Value}, skipping popup");
						Plugin.SpectatorEnabled.Value = lockedRoleForJoiner.Value;
						return false;
					}
					int liveCountAtJoin = MidRunJoinGate.LiveCountAtJoin;
					MidRunJoinGate.LiveCountAtJoin = -1;
					if (liveCountAtJoin >= 0 && liveCountAtJoin >= 4)
					{
						Plugin.Trace($"[trace] mid-run joiner: live cap saturated ({liveCountAtJoin}/{4}); auto-spectating, skipping popup");
						Plugin.SpectatorEnabled.Value = true;
						return false;
					}
					<popup>5__2 = MidRunJoinPopup.Instance;
					if ((Object)(object)<popup>5__2 == (Object)null)
					{
						Plugin.TraceWarn("[trace] mid-run wait coroutine: MidRunJoinPopup.Instance missing; defaulting to live join");
						Plugin.SpectatorEnabled.Value = false;
						return false;
					}
					<popup>5__2.Show();
					Plugin.Trace("[trace] mid-run wait coroutine: popup shown, waiting for click");
					break;
				}
				case 1:
					<>1__state = -1;
					break;
				}
				if (<popup>5__2.IsShowing && !<popup>5__2.HasChoice)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				if (!<popup>5__2.HasChoice)
				{
					Plugin.TraceWarn("[trace] mid-run wait coroutine: popup hid without a click; defaulting to live join");
					Plugin.SpectatorEnabled.Value = false;
					return false;
				}
				<popup>5__2.Hide();
				Plugin.Trace($"[trace] mid-run popup choice received: spectate={<popup>5__2.ChoiceIsSpectate}");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[HarmonyPrefix]
		private static void Prefix(ref IEnumerator[] processes)
		{
			string pendingSceneName = MidRunJoinGate.PendingSceneName;
			MidRunJoinGate.PendingSceneName = null;
			bool? lockedRoleForJoiner = MidRunJoinGate.LockedRoleForJoiner;
			int liveCountAtJoin = MidRunJoinGate.LiveCountAtJoin;
			MidRunJoinGate.LockedRoleForJoiner = null;
			MidRunJoinGate.LiveCountAtJoin = -1;
			if (pendingSceneName != null && !(pendingSceneName == "Airport") && processes != null)
			{
				MidRunJoinGate.LockedRoleForJoiner = lockedRoleForJoiner;
				MidRunJoinGate.LiveCountAtJoin = liveCountAtJoin;
				Plugin.Trace("[trace] injecting mid-run popup wait into Load() processes for scene '" + pendingSceneName + "'");
				IEnumerator[] array = new IEnumerator[processes.Length + 1];
				array[0] = WaitForMidRunChoice();
				for (int i = 0; i < processes.Length; i++)
				{
					array[i + 1] = processes[i];
				}
				processes = array;
			}
		}

		[IteratorStateMachine(typeof(<WaitForMidRunChoice>d__1))]
		private static IEnumerator WaitForMidRunChoice()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WaitForMidRunChoice>d__1(0);
		}
	}
	[HarmonyPatch(typeof(Character), "Start")]
	internal static class Patch_Character_Start_ValidateOwner
	{
		[CompilerGenerated]
		private sealed class <DeferredValidate>d__1 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Character c;

			public Player owner;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DeferredValidate>d__1(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<>2__current = (object)new WaitForEndOfFrame();
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					if ((Object)(object)c == (Object)null || owner == null)
					{
						return false;
					}
					if (SpectatorState.HasGhostSpectatorMod(owner))
					{
						Plugin.TraceDebug($"[trace] deferred validate: {owner.NickName} (#{owner.ActorNumber}) property arrived in deferral window, no action");
						return false;
					}
					Plugin.Trace($"[trace] unmoded joiner confirmed at Character.Start+1frame: {owner.NickName} (#{owner.ActorNumber}); hiding visuals");
					SpectatorState.HideCharacterVisuals(c);
					if (PhotonNetwork.IsMasterClient)
					{
						Plugin.Trace($"[trace] kicking {owner.NickName} (#{owner.ActorNumber}): GhostSpectator mod not installed");
						SpectatorState.TryAddConnectionLogMessage(owner.NickName + " was disconnected: GhostSpectator mod required");
						try
						{
							PlayerHandler.Kick(owner.ActorNumber);
						}
						catch (Exception ex)
						{
							Plugin.Log.LogError((object)("[trace] PlayerHandler.Kick threw on " + owner.NickName + ": " + ex.GetType().Name + ": " + ex.Message));
						}
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && (!((Object)(object)Character.localCharacter != (Object)null) || !((Object)(object)__instance == (Object)(object)Character.localCharacter)) && SpectatorState.TryGetOwner(__instance, out Player owner) && !SpectatorState.HasGhostSpectatorMod(owner))
			{
				((MonoBehaviour)__instance).StartCoroutine(DeferredValidate(__instance, owner));
			}
		}

		[IteratorStateMachine(typeof(<DeferredValidate>d__1))]
		private static IEnumerator DeferredValidate(Character c, Player owner)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DeferredValidate>d__1(0)
			{
				c = c,
				owner = owner
			};
		}
	}
	[HarmonyPatch(typeof(Character), "RPCA_Revive")]
	internal static class Patch_Character_RPCA_Revive
	{
		[HarmonyPrefix]
		private static bool Prefix(Character __instance)
		{
			if (SpectatorState.IsTrustedSpectator(__instance))
			{
				Plugin.TraceDebug("GhostSpectator: blocked RPCA_Revive on spectator.");
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Character), "RPCA_ReviveAtPosition")]
	internal static class Patch_Character_RPCA_ReviveAtPosition
	{
		[HarmonyPrefix]
		private static bool Prefix(Character __instance)
		{
			if (SpectatorState.IsTrustedSpectator(__instance))
			{
				Plugin.TraceDebug("GhostSpectator: blocked RPCA_ReviveAtPosition on spectator.");
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(RespawnRandomScout), "Start")]
	internal static class Patch_RespawnRandomScout_Start
	{
		[HarmonyPrefix]
		private static bool Prefix(RespawnRandomScout __instance)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			if (PhotonNetwork.IsMasterClient)
			{
				List<Character> list = new List<Character>();
				foreach (Character allCharacter in Character.AllCharacters)
				{
					if ((allCharacter.data.dead || allCharacter.data.fullyPassedOut) && !SpectatorState.IsTrustedSpectator(allCharacter))
					{
						list.Add(allCharacter);
					}
				}
				if (list.Count > 0)
				{
					((MonoBehaviourPun)Util.RandomSelection<Character>((IEnumerable<Character>)list, (Func<Character, int>)((Character _) => 1))).photonView.RPC("RPCA_ReviveAtPosition", (RpcTarget)0, new object[3]
					{
						((Component)__instance).transform.position,
						false,
						-1
					});
				}
			}
			Object.Destroy((Object)(object)((Component)__instance).gameObject);
			return false;
		}
	}
	internal static class RoleLock
	{
		internal const string RoomRunRolesKey = "GhostSpectator.RunRoles";

		internal static readonly Dictionary<string, bool> RunRoles = new Dictionary<string, bool>();

		internal static Hashtable SerializeRunRolesToHashtable()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			Hashtable val = new Hashtable();
			lock (RunRoles)
			{
				foreach (KeyValuePair<string, bool> runRole in RunRoles)
				{
					val[(object)runRole.Key] = runRole.Value;
				}
				return val;
			}
		}

		internal static void DeserializeRunRolesFromHashtable(object? raw)
		{
			lock (RunRoles)
			{
				RunRoles.Clear();
				Hashtable val = (Hashtable)((raw is Hashtable) ? raw : null);
				if (val == null)
				{
					return;
				}
				foreach (object key2 in ((Dictionary<object, object>)(object)val).Keys)
				{
					if (key2 is string key && val[key2] is bool value)
					{
						RunRoles[key] = value;
					}
				}
			}
		}

		internal static string SerializeRunRolesToString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			lock (RunRoles)
			{
				bool flag = true;
				foreach (KeyValuePair<string, bool> runRole in RunRoles)
				{
					if (!flag)
					{
						stringBuilder.Append(';');
					}
					stringBuilder.Append(runRole.Key);
					stringBuilder.Append(':');
					stringBuilder.Append(runRole.Value ? '1' : '0');
					flag = false;
				}
			}
			return stringBuilder.ToString();
		}

		internal static void DeserializeRunRolesFromString(string? s, Dictionary<string, bool> target)
		{
			target.Clear();
			if (string.IsNullOrEmpty(s))
			{
				return;
			}
			string[] array = s.Split(';');
			string[] array2 = array;
			foreach (string text in array2)
			{
				int num = text.IndexOf(':');
				if (num > 0 && num != text.Length - 1)
				{
					string key = text.Substring(0, num);
					string text2 = text.Substring(num + 1);
					if (text2 == "1")
					{
						target[key] = true;
					}
					else if (text2 == "0")
					{
						target[key] = false;
					}
				}
			}
		}

		internal static void PublishRunRolesToNetwork()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0029: Expected O, but got Unknown
			if (PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null)
			{
				try
				{
					Hashtable value = SerializeRunRolesToHashtable();
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"GhostSpectator.RunRoles", (object)value);
					Hashtable val2 = val;
					PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("[trace] PublishRunRolesToNetwork SetCustomProperties failed: " + ex.GetType().Name + ": " + ex.Message));
				}
				TryWriteRunRolesToSteamLobby();
			}
		}

		internal static void TryWriteRunRolesToSteamLobby()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SteamLobbyHandler service = GameHandler.GetService<SteamLobbyHandler>();
				if (service != null)
				{
					CSteamID lobbySteamId = service.LobbySteamId;
					if (!(lobbySteamId == CSteamID.Nil))
					{
						string text = SerializeRunRolesToString();
						SteamMatchmaking.SetLobbyData(lobbySteamId, "GhostSpectator.RunRoles", text);
						Plugin.TraceDebug($"[trace] mirrored RunRoles to Steam lobby data ({text.Length} chars)");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.TraceDebug("[trace] TryWriteRunRolesToSteamLobby failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		internal static bool RunRolesHashtableMatchesInMemory(Hashtable? inbound)
		{
			if (inbound == null)
			{
				return RunRoles.Count == 0;
			}
			lock (RunRoles)
			{
				if (((Dictionary<object, object>)(object)inbound).Count != RunRoles.Count)
				{
					return false;
				}
				foreach (object key2 in ((Dictionary<object, object>)(object)inbound).Keys)
				{
					if (!(key2 is string key))
					{
						return false;
					}
					if (!RunRoles.TryGetValue(key, out var value))
					{
						return false;
					}
					if (!(inbound[key2] is bool flag))
					{
						return false;
					}
					if (flag != value)
					{
						return false;
					}
				}
				return true;
			}
		}
	}
	internal static class KioskGate
	{
		internal static (bool passed, string? refuseMessage) CheckRunStartPreconditions()
		{
			if (PhotonNetwork.CurrentRoom == null)
			{
				return (true, null);
			}
			foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values)
			{
				if (value != null && !SpectatorState.HasGhostSpectatorMod(value))
				{
					string text = (string.IsNullOrEmpty(value.NickName) ? "(unnamed)" : value.NickName);
					return (false, "Can't start: " + text + " doesn't have GhostSpectator installed. All players must install the mod from Thunderstore.");
				}
			}
			if (RoomCallbackHandler.CountNonSpectators() == 0)
			{
				return (false, "Can't start: every player is set to Ghost Spectator. Toggle off in the airport panel, or wait for a non-spectator to join.");
			}
			return (true, null);
		}

		internal static void OnRunStartedMaster()
		{
			SpectatorState.RunHasEnded = false;
			Patch_Character_EndGame.ResetSuppressionTimer();
			lock (RoleLock.RunRoles)
			{
				RoleLock.RunRoles.Clear();
				foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values)
				{
					if (value != null && !string.IsNullOrEmpty(value.UserId))
					{
						RoleLock.RunRoles[value.UserId] = SpectatorState.ClaimsSpectator(value);
					}
				}
			}
			Plugin.Trace($"[trace] run started: snapshotted {RoleLock.RunRoles.Count} player(s) into RunRoles");
			RoleLock.PublishRunRolesToNetwork();
		}
	}
	[HarmonyPatch(typeof(AirportCheckInKiosk), "StartGame")]
	internal static class Patch_AirportCheckInKiosk_StartGame
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			string[] obj = new string[6]
			{
				$"[trace] StartGame prefix fired. InRoom={PhotonNetwork.InRoom}, ",
				$"SpectatorEnabled={Plugin.SpectatorEnabled.Value}, ",
				"CurrentRoomPlayerCount=",
				null,
				null,
				null
			};
			Room currentRoom = PhotonNetwork.CurrentRoom;
			obj[3] = ((currentRoom != null) ? currentRoom.PlayerCount.ToString() : null) ?? "n/a";
			obj[4] = ", ";
			obj[5] = $"NonSpectators={RoomCallbackHandler.CountNonSpectators()}";
			Plugin.TraceDebug(string.Concat(obj));
			var (flag, text) = KioskGate.CheckRunStartPreconditions();
			if (!flag)
			{
				Plugin.TraceWarn("GhostSpectator: clicker-side gate refused: " + text);
				SpectatorState.RefuseKiosk(text);
				return false;
			}
			SpectatorState.RunHasEnded = false;
			return true;
		}
	}
	[HarmonyPatch(typeof(AirportCheckInKiosk), "LoadIslandMaster")]
	internal static class Patch_AirportCheckInKiosk_LoadIslandMaster
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			Plugin.TraceDebug($"[trace] LoadIslandMaster prefix fired. IsMasterClient={PhotonNetwork.IsMasterClient}");
			if (!PhotonNetwork.IsMasterClient)
			{
				return true;
			}
			var (flag, text) = KioskGate.CheckRunStartPreconditions();
			if (!flag)
			{
				Plugin.TraceWarn("GhostSpectator: master-side gate refused: " + text);
				SpectatorState.RefuseKiosk(text);
				return false;
			}
			KioskGate.OnRunStartedMaster();
			return true;
		}
	}
	[HarmonyPatch(typeof(PeakHandler), "SetCosmetics")]
	internal static class Patch_PeakHandler_SetCosmetics
	{
		[HarmonyPrefix]
		private static bool Prefix(List<Character> characters)
		{
			if (characters == null)
			{
				return true;
			}
			for (int i = 0; i < characters.Count; i++)
			{
				Character val = characters[i];
				if ((Object)(object)val != (Object)null && val.refs != null && (Object)(object)val.refs.stats != (Object)null && val.refs.stats.won)
				{
					return true;
				}
			}
			Plugin.Trace("[trace] SetCosmetics: nobody won this run, skipping cosmetic setup to keep EndCutscene from crashing.");
			return false;
		}
	}
	[HarmonyPatch(typeof(AchievementManager), "ThrowAchievement")]
	internal static class Patch_AchievementManager_ThrowAchievement
	{
		[HarmonyPrefix]
		private static bool Prefix(ACHIEVEMENTTYPE type)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.SpectatorEnabled.Value)
			{
				return true;
			}
			Plugin.TraceDebug($"[trace] blocked achievement {type} on spectator");
			return false;
		}
	}
	[HarmonyPatch(typeof(AchievementManager), "TryCompleteAscent")]
	internal static class Patch_AchievementManager_TryCompleteAscent
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			if (!Plugin.SpectatorEnabled.Value)
			{
				return true;
			}
			Plugin.TraceDebug("[trace] blocked TryCompleteAscent on spectator");
			return false;