Decompiled source of LiveSpectate v1.0.0

LiveSpectate.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LiveSpectate")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+1393328e73447401cf7c63b113aadae58e5969be")]
[assembly: AssemblyProduct("LiveSpectate")]
[assembly: AssemblyTitle("LiveSpectate")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 LiveSpectate
{
	internal readonly struct LiveSpectateCameraCloneResult
	{
		internal GameObject RootObject { get; }

		internal Camera RootCamera { get; }

		internal LiveSpectateCameraCloneResult(GameObject rootObject, Camera rootCamera)
		{
			RootObject = rootObject;
			RootCamera = rootCamera;
		}
	}
	internal static class LiveSpectateCameraFactory
	{
		private const string PostProcessLayerTypeName = "UnityEngine.Rendering.PostProcessing.PostProcessLayer";

		internal static LiveSpectateCameraCloneResult Create(Camera source, float liveFieldOfView)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			GameObject val = new GameObject("LiveSpectate Local Camera");
			Object.DontDestroyOnLoad((Object)(object)val);
			Camera val2 = val.AddComponent<Camera>();
			CopyCameraCoreFields(source, val2, liveFieldOfView, source.depth + 0.01f);
			if ((Object)(object)source.targetTexture != (Object)null)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)("LiveSpectate source main camera has non-null targetTexture '" + ((Object)source.targetTexture).name + "'; it is intentionally copied/shared to keep RenderTextureMain output updating during live spectate."));
				}
			}
			DumpSourceCameraComponentDiagnostics(source);
			CopyPostProcessLayerIfPresent(source, val2);
			int num = CloneChildCameras(source, val, val2);
			LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate cloned {num} child camera(s) from original main camera hierarchy.");
			return new LiveSpectateCameraCloneResult(val, val2);
		}

		private static int CloneChildCameras(Camera source, GameObject liveCameraObject, Camera liveCamera)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00b9: 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_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: 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_01c6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null || (Object)(object)liveCameraObject == (Object)null)
			{
				return 0;
			}
			int num = 0;
			float num2 = (((Object)(object)liveCamera != (Object)null) ? (liveCamera.depth - source.depth) : 0f);
			Camera[] componentsInChildren = ((Component)source).GetComponentsInChildren<Camera>(true);
			foreach (Camera val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)source)
				{
					continue;
				}
				GameObject val2 = new GameObject("LiveSpectate " + ((Object)val).name);
				val2.transform.SetParent(liveCameraObject.transform, false);
				val2.transform.localPosition = ((Component)source).transform.InverseTransformPoint(((Component)val).transform.position);
				val2.transform.localRotation = Quaternion.Inverse(((Component)source).transform.rotation) * ((Component)val).transform.rotation;
				val2.transform.localScale = GetRelativeLossyScale(((Component)val).transform, ((Component)source).transform);
				Camera val3 = val2.AddComponent<Camera>();
				CopyCameraCoreFields(val, val3, val.fieldOfView, val.depth + num2);
				((Behaviour)val3).enabled = ((Behaviour)val).enabled;
				CopyPostProcessLayerIfPresent(val, val3);
				num++;
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate cloned child camera '{((Object)val).name}' -> '{((Object)val3).name}' fov={val3.fieldOfView:0.###}, depth={val3.depth:0.###}, cullingMask={val3.cullingMask}, clearFlags={val3.clearFlags}, enabled={((Behaviour)val3).enabled}.");
					if ((Object)(object)val.targetTexture != (Object)null)
					{
						LiveSpectatePlugin.Log.LogInfo((object)("LiveSpectate source child camera '" + ((Object)val).name + "' has non-null targetTexture '" + ((Object)val.targetTexture).name + "'; it is intentionally copied/shared to cloned child camera '" + ((Object)val3).name + "' to keep RenderTextureMain output updating during live spectate."));
					}
				}
			}
			return num;
		}

		private static Vector3 GetRelativeLossyScale(Transform child, Transform root)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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_0019: 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_0085: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)child == (Object)null || (Object)(object)root == (Object)null)
			{
				return Vector3.one;
			}
			Vector3 lossyScale = child.lossyScale;
			Vector3 lossyScale2 = root.lossyScale;
			Vector3 localScale = child.localScale;
			return new Vector3(SafeDivideScale(lossyScale.x, lossyScale2.x, localScale.x), SafeDivideScale(lossyScale.y, lossyScale2.y, localScale.y), SafeDivideScale(lossyScale.z, lossyScale2.z, localScale.z));
		}

		private static float SafeDivideScale(float childScale, float rootScale, float fallback)
		{
			if (!LiveSpectateMath.IsFinite(childScale) || !LiveSpectateMath.IsFinite(rootScale) || Mathf.Approximately(rootScale, 0f))
			{
				return LiveSpectateMath.IsFinite(fallback) ? fallback : 1f;
			}
			float num = childScale / rootScale;
			return LiveSpectateMath.IsFinite(num) ? num : (LiveSpectateMath.IsFinite(fallback) ? fallback : 1f);
		}

		private static void CopyCameraCoreFields(Camera source, Camera destination, float fieldOfView, float depth)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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)
			destination.targetTexture = source.targetTexture;
			destination.cullingMask = source.cullingMask;
			destination.clearFlags = source.clearFlags;
			destination.backgroundColor = source.backgroundColor;
			destination.nearClipPlane = source.nearClipPlane;
			destination.farClipPlane = source.farClipPlane;
			destination.fieldOfView = fieldOfView;
			destination.depth = depth;
			destination.renderingPath = source.renderingPath;
			destination.allowHDR = source.allowHDR;
			destination.allowMSAA = source.allowMSAA;
			destination.useOcclusionCulling = source.useOcclusionCulling;
			destination.stereoTargetEye = source.stereoTargetEye;
		}

		private static void CopyPostProcessLayerIfPresent(Camera source, Camera destination)
		{
			Type type = LiveSpectateReflection.FindType("UnityEngine.Rendering.PostProcessing.PostProcessLayer");
			if (type == null)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer copy skipped; type not found.");
				}
				return;
			}
			Component component = ((Component)source).GetComponent(type);
			if ((Object)(object)component == (Object)null)
			{
				ConfigEntry<bool> verboseDiagnostics2 = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics2 != null && verboseDiagnostics2.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer copy skipped; source direct component not found.");
				}
				return;
			}
			Component val = null;
			try
			{
				val = ((Component)destination).gameObject.AddComponent(type);
				Behaviour val2 = (Behaviour)(object)((component is Behaviour) ? component : null);
				bool enabled = val2 != null && val2.enabled;
				Behaviour val3 = (Behaviour)(object)((val is Behaviour) ? val : null);
				if (val3 != null)
				{
					val3.enabled = false;
				}
				if (!TryCopyComponentWithJson(component, val))
				{
					Object.DestroyImmediate((Object)(object)val);
					LiveSpectatePlugin.Log.LogWarning((object)"LiveSpectate PostProcessLayer copy skipped; serialized copy failed.");
					return;
				}
				NormalizePostProcessLayerVolumeTrigger(source, destination, val);
				Behaviour val4 = (Behaviour)(object)((val is Behaviour) ? val : null);
				if (val4 != null)
				{
					val4.enabled = enabled;
				}
				LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate copied direct PostProcessLayer to live camera.");
			}
			catch (Exception ex)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val);
				}
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate PostProcessLayer copy failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static bool TryCopyComponentWithJson(Component source, Component destination)
		{
			try
			{
				Type type = LiveSpectateReflection.FindType("UnityEngine.JsonUtility");
				MethodInfo methodInfo = type?.GetMethod("ToJson", new Type[1] { typeof(object) });
				MethodInfo methodInfo2 = type?.GetMethod("FromJsonOverwrite", new Type[2]
				{
					typeof(string),
					typeof(object)
				});
				if (methodInfo == null || methodInfo2 == null)
				{
					return false;
				}
				string text = methodInfo.Invoke(null, new object[1] { source }) as string;
				if (string.IsNullOrEmpty(text))
				{
					return false;
				}
				methodInfo2.Invoke(null, new object[2] { text, destination });
				return true;
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("PostProcessLayer JsonUtility copy failed: " + ex.GetType().Name + ": " + ex.Message));
				}
				return false;
			}
		}

		private static void NormalizePostProcessLayerVolumeTrigger(Camera source, Camera destination, Component destinationLayer)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)destination == (Object)null || (Object)(object)destinationLayer == (Object)null)
			{
				return;
			}
			object memberValue = LiveSpectateReflection.GetMemberValue(destinationLayer, "volumeTrigger");
			if ((memberValue == null || memberValue == ((Component)source).transform) && LiveSpectateReflection.TrySetMemberValue(destinationLayer, "volumeTrigger", ((Component)destination).transform))
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer volumeTrigger normalized to live camera transform.");
				}
			}
		}

		private static void DumpSourceCameraComponentDiagnostics(Camera source)
		{
			ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
			if (verboseDiagnostics == null || !verboseDiagnostics.Value || (Object)(object)source == (Object)null)
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("LiveSpectate source camera components: ");
				AppendComponentTypeNames(stringBuilder, ((Component)source).GetComponents<Component>());
				Camera[] componentsInChildren = ((Component)source).GetComponentsInChildren<Camera>(true);
				stringBuilder.Append(" | child cameras: ");
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Camera val = componentsInChildren[i];
					if (!((Object)(object)val == (Object)null))
					{
						if (i > 0)
						{
							stringBuilder.Append("; ");
						}
						stringBuilder.Append(((Object)val).name);
						stringBuilder.Append(" [");
						AppendComponentTypeNames(stringBuilder, ((Component)val).GetComponents<Component>());
						stringBuilder.Append(']');
					}
				}
				LiveSpectatePlugin.Log.LogInfo((object)stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate camera component diagnostics failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void AppendComponentTypeNames(StringBuilder builder, Component[] components)
		{
			if (components == null || components.Length == 0)
			{
				builder.Append("<none>");
				return;
			}
			for (int i = 0; i < components.Length; i++)
			{
				if (i > 0)
				{
					builder.Append(", ");
				}
				Component val = components[i];
				builder.Append(((Object)(object)val != (Object)null) ? ((object)val).GetType().FullName : "<null>");
			}
		}
	}
	internal sealed class LiveSpectateCameraFollower
	{
		private const float DefaultDistance = 3f;

		private const float MinDistance = 1f;

		private const float MaxDistance = 6f;

		private const float ScrollDistanceMultiplier = 0.0025f;

		private const float DistanceSmoothing = 5f;

		private const float FocusSmoothing = 10f;

		private const float VehicleFocusSmoothing = 20f;

		private const float PitchClamp = 70f;

		private float yaw;

		private float pitch;

		private float cameraDistance = 3f;

		private Vector3 smoothedPivotPosition;

		private Quaternion smoothedPivotRotation = Quaternion.identity;

		private float smoothedLocalDistance = 3f;

		internal void Initialize(Camera liveCamera, Quaternion basis, Vector3 focus)
		{
			//IL_0004: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			yaw = ((Quaternion)(ref basis)).eulerAngles.y;
			pitch = NormalizePitch(((Quaternion)(ref basis)).eulerAngles.x);
			cameraDistance = 3f;
			smoothedPivotPosition = focus;
			smoothedPivotRotation = basis;
			smoothedLocalDistance = 3f;
			ApplyLiveCameraTransform(liveCamera, smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance, smoothedPivotRotation);
		}

		internal void Update(Camera liveCamera, PlayerAvatar targetPlayer)
		{
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: 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_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0305: Unknown result type (might be due to invalid IL or missing references)
			//IL_0312: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0380: Unknown result type (might be due to invalid IL or missing references)
			//IL_0385: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_039f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
			float num = SemiFunc.InputMouseX();
			float num2 = SemiFunc.InputMouseY();
			float num3 = SemiFunc.InputScrollY();
			if (!LiveSpectateMath.IsFinite(num))
			{
				num = 0f;
			}
			if (!LiveSpectateMath.IsFinite(num2))
			{
				num2 = 0f;
			}
			if (!LiveSpectateMath.IsFinite(num3))
			{
				num3 = 0f;
			}
			if ((Object)(object)CameraAim.Instance != (Object)null && LiveSpectateReflection.GetBoolMember(CameraAim.Instance, "overrideAimStop"))
			{
				num = 0f;
				num2 = 0f;
				num3 = 0f;
			}
			float num4 = (((Object)(object)CameraAim.Instance != (Object)null) ? CameraAim.Instance.AimSpeedMouse : 1f);
			float num5 = (((Object)(object)GameplayManager.instance != (Object)null && LiveSpectateReflection.GetBoolMember(GameplayManager.instance, "aimInvertVertical")) ? (-1f) : 1f);
			yaw += num * num4 * 1.5f;
			pitch += (0f - num2) * num4 * 1.5f * num5;
			pitch = Mathf.Clamp(pitch, -70f, 70f);
			if (yaw > 360f)
			{
				yaw -= 360f;
			}
			else if (yaw < -360f)
			{
				yaw += 360f;
			}
			if (!LiveSpectateMath.IsFinite(cameraDistance))
			{
				cameraDistance = 3f;
			}
			cameraDistance = Mathf.Clamp(cameraDistance - num3 * 0.0025f, 1f, 6f);
			if (!LiveSpectateMath.IsFinite(cameraDistance))
			{
				cameraDistance = 3f;
			}
			Quaternion rotation = Quaternion.Euler(pitch, yaw, 0f);
			Vector3 position = GetTargetFocusPoint(targetPlayer);
			if (!LiveSpectateMath.IsFinite(position) || !LiveSpectateMath.IsFinite(rotation))
			{
				GetFallbackCameraTransform(liveCamera, out position, out rotation);
				smoothedPivotPosition = position;
				smoothedPivotRotation = rotation;
				yaw = ((Quaternion)(ref rotation)).eulerAngles.y;
				pitch = NormalizePitch(((Quaternion)(ref rotation)).eulerAngles.x);
			}
			float num6 = (IsPlayerInVehicle(targetPlayer) ? 20f : 10f);
			float num7 = Mathf.Clamp01(Time.deltaTime * num6);
			smoothedPivotPosition = Vector3.Lerp(smoothedPivotPosition, position, num7);
			float num8 = (((Object)(object)GameplayManager.instance != (Object)null) ? LiveSpectateReflection.GetFloatMember(GameplayManager.instance, "cameraSmoothing", 0f) : 0f);
			float num9 = Mathf.Lerp(50f, 6.25f, Mathf.Clamp01(num8 / 100f));
			float num10 = Mathf.Clamp01(Time.deltaTime * num9);
			smoothedPivotRotation = Quaternion.Lerp(smoothedPivotRotation, rotation, num10);
			if (!LiveSpectateMath.IsFinite(smoothedPivotPosition) || !LiveSpectateMath.IsFinite(smoothedPivotRotation))
			{
				GetFallbackCameraTransform(liveCamera, out smoothedPivotPosition, out smoothedPivotRotation);
			}
			float num11 = ResolveCameraDistance(smoothedPivotPosition, smoothedPivotRotation, cameraDistance);
			smoothedLocalDistance = Mathf.Lerp(smoothedLocalDistance, num11, Mathf.Clamp01(Time.deltaTime * 5f));
			Vector3 position2 = smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance;
			ApplyLiveCameraTransform(liveCamera, position2, smoothedPivotRotation);
		}

		internal void ResetForTarget(PlayerAvatar target, Camera liveCamera, bool applyTransform)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: 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_000a: 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)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			Vector3 targetFocusPoint = GetTargetFocusPoint(target);
			smoothedPivotPosition = targetFocusPoint;
			smoothedPivotRotation = Quaternion.Euler(pitch, yaw, 0f);
			smoothedLocalDistance = Mathf.Clamp(cameraDistance, 1f, 6f);
			if (applyTransform && (Object)(object)liveCamera != (Object)null)
			{
				Vector3 position = smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance;
				ApplyLiveCameraTransform(liveCamera, position, smoothedPivotRotation);
			}
		}

		internal void Reset()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//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)
			smoothedPivotPosition = Vector3.zero;
			smoothedPivotRotation = Quaternion.identity;
			smoothedLocalDistance = 3f;
		}

		internal static Vector3 GetTargetFocusPoint(PlayerAvatar player)
		{
			//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_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: 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)
			if ((Object)(object)player == (Object)null)
			{
				return Vector3.zero;
			}
			try
			{
				ItemVehicle vehicleForPlayer = ItemVehicle.GetVehicleForPlayer(player);
				Transform vehicleMeshTransform = GetVehicleMeshTransform(vehicleForPlayer);
				if ((Object)(object)vehicleMeshTransform != (Object)null)
				{
					return vehicleMeshTransform.position + Vector3.up * 0.8f;
				}
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("Vehicle focus lookup failed: " + ex.GetType().Name));
				}
			}
			if ((Object)(object)player.spectatePoint != (Object)null)
			{
				return player.spectatePoint.position;
			}
			return ((Component)player).transform.position + Vector3.up * 1.6f;
		}

		private static float ResolveCameraDistance(Vector3 pivotPosition, Quaternion pivotRotation, float distance)
		{
			//IL_0001: 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_0059: 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_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)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (!LiveSpectateMath.IsFinite(pivotPosition) || !LiveSpectateMath.IsFinite(pivotRotation))
			{
				return GetSafeCameraDistance(distance);
			}
			Vector3 direction = -(pivotRotation * Vector3.forward);
			float distance2 = GetSafeCameraDistance(distance);
			try
			{
				LayerMask val = SemiFunc.LayerMaskGetVisionObstruct();
				distance2 = ResolveCameraDistanceWithMask(pivotPosition, direction, distance2, LayerMask.op_Implicit(val));
			}
			catch
			{
				try
				{
					distance2 = ResolveCameraDistanceWithMask(pivotPosition, direction, distance2, -5);
				}
				catch
				{
					distance2 = GetSafeCameraDistance(distance);
				}
			}
			return GetSafeCameraDistance(distance2);
		}

		private static float ResolveCameraDistanceWithMask(Vector3 origin, Vector3 direction, float distance, int layerMask)
		{
			//IL_0008: 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_0030: 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_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)
			float num = GetSafeCameraDistance(distance);
			if (!LiveSpectateMath.IsFinite(origin) || !LiveSpectateMath.IsFinite(direction) || !LiveSpectateMath.IsFinite(num))
			{
				return num;
			}
			RaycastHit[] array = Physics.SphereCastAll(origin, 0.1f, direction, num, layerMask, (QueryTriggerInteraction)1);
			RaycastHit[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				RaycastHit val = array2[i];
				if (!((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) && !(((RaycastHit)(ref val)).distance <= 0f) && !ShouldIgnoreCameraCollision(((RaycastHit)(ref val)).collider))
				{
					num = Mathf.Min(num, Mathf.Max(1f, ((RaycastHit)(ref val)).distance));
				}
			}
			return GetSafeCameraDistance(num);
		}

		private static float GetSafeCameraDistance(float distance)
		{
			return LiveSpectateMath.IsFinite(distance) ? Mathf.Clamp(distance, 1f, 6f) : 3f;
		}

		private static bool ShouldIgnoreCameraCollision(Collider collider)
		{
			Transform val = ((Component)collider).transform;
			while ((Object)(object)val != (Object)null)
			{
				GameObject gameObject = ((Component)val).gameObject;
				if ((Object)(object)gameObject.GetComponent<PlayerHealthGrab>() != (Object)null || (Object)(object)gameObject.GetComponent<PlayerAvatar>() != (Object)null || (Object)(object)gameObject.GetComponent<PlayerTumble>() != (Object)null || (Object)(object)gameObject.GetComponent<ItemVehicle>() != (Object)null || (Object)(object)gameObject.GetComponent<EnemyRigidbody>() != (Object)null || (Object)(object)gameObject.GetComponent("SpectateCameraCollisionIgnore") != (Object)null)
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private static void ApplyLiveCameraTransform(Camera liveCamera, Vector3 position, Quaternion rotation)
		{
			//IL_0001: 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_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (!LiveSpectateMath.IsFinite(position) || !LiveSpectateMath.IsFinite(rotation))
			{
				GetFallbackCameraTransform(liveCamera, out position, out rotation);
			}
			if ((Object)(object)liveCamera != (Object)null)
			{
				((Component)liveCamera).transform.SetPositionAndRotation(position, rotation);
			}
		}

		private static void GetFallbackCameraTransform(Camera liveCamera, out Vector3 position, out Quaternion rotation)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: 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_00ba: 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_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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_00ad: 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)
			if ((Object)(object)liveCamera != (Object)null && LiveSpectateMath.IsFinite(((Component)liveCamera).transform.position) && LiveSpectateMath.IsFinite(((Component)liveCamera).transform.rotation))
			{
				position = ((Component)liveCamera).transform.position;
				rotation = ((Component)liveCamera).transform.rotation;
				return;
			}
			GameDirector instance = GameDirector.instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				Camera mainCamera = instance.MainCamera;
				obj = ((mainCamera != null) ? ((Component)mainCamera).transform : null);
			}
			Transform val = (Transform)obj;
			if ((Object)(object)val != (Object)null && LiveSpectateMath.IsFinite(val.position) && LiveSpectateMath.IsFinite(val.rotation))
			{
				position = val.position;
				rotation = val.rotation;
			}
			else
			{
				position = Vector3.zero;
				rotation = Quaternion.identity;
			}
		}

		private static bool IsPlayerInVehicle(PlayerAvatar player)
		{
			try
			{
				ItemVehicle vehicleForPlayer = ItemVehicle.GetVehicleForPlayer(player);
				return (Object)(object)vehicleForPlayer != (Object)null && (Object)(object)GetVehicleMeshTransform(vehicleForPlayer) != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static Transform GetVehicleMeshTransform(ItemVehicle vehicle)
		{
			if ((Object)(object)vehicle == (Object)null)
			{
				return null;
			}
			object memberValue = LiveSpectateReflection.GetMemberValue(vehicle, "meshTransform");
			return (Transform)(((memberValue is Transform) ? memberValue : null) ?? ((Component)vehicle).transform);
		}

		private static float NormalizePitch(float value)
		{
			return (value > 180f) ? (value - 360f) : value;
		}
	}
	public sealed class LiveSpectateController : MonoBehaviour
	{
		private const float ToggleDebounceSeconds = 0.25f;

		private const float InputDisableSeconds = 0.25f;

		private readonly LiveSpectateResourceRestorer resourceRestorer = new LiveSpectateResourceRestorer();

		private readonly LiveSpectateCameraFollower cameraFollower = new LiveSpectateCameraFollower();

		private float nextToggleTime;

		private bool liveSpectateActive;

		private PlayerAvatar owner;

		private PlayerAvatar targetPlayer;

		private GameObject liveCameraObject;

		private Camera liveCamera;

		internal static LiveSpectateController Instance { get; set; }

		private void Update()
		{
			if (liveSpectateActive)
			{
				FreezeLocalControls();
				HandleSpectateSwitchInput();
				LiveSpectateHudService.UpdateLiveSpectateHud(targetPlayer);
				if (!LiveSpectateInputPolicy.CanContinueLiveSpectate(owner, out var reason))
				{
					LiveSpectateLog.LogLifecycle("Continuation guard stopping live spectate. reason=" + reason + ".", "Continuation guard stopping local live spectate. reason=" + reason + ".");
					StopLiveSpectate();
				}
			}
			Keyboard current = Keyboard.current;
			if (((current != null) ? current.kKey : null) == null || !((ButtonControl)Keyboard.current.kKey).wasPressedThisFrame)
			{
				return;
			}
			if (liveSpectateActive)
			{
				string allowDenyReason = "Allowed: active live spectate manual stop";
				if (Time.unscaledTime < nextToggleTime)
				{
					allowDenyReason = $"Denied: debounce active until {nextToggleTime:0.000} (now {Time.unscaledTime:0.000})";
					LogKPressDiagnostic(allowDenyReason);
				}
				else
				{
					LogKPressDiagnostic(allowDenyReason);
					nextToggleTime = Time.unscaledTime + 0.25f;
					StopLiveSpectate();
				}
				return;
			}
			string reason2;
			bool flag = LiveSpectateInputPolicy.CanToggleNow(out reason2);
			if (flag && Time.unscaledTime < nextToggleTime)
			{
				flag = false;
				reason2 = $"Denied: debounce active until {nextToggleTime:0.000} (now {Time.unscaledTime:0.000})";
			}
			if (flag)
			{
				flag = LiveSpectateInputPolicy.CanStart(PlayerAvatar.instance, out reason2);
			}
			LogKPressDiagnostic(reason2);
			if (flag)
			{
				nextToggleTime = Time.unscaledTime + 0.25f;
				TryStartLiveSpectate();
			}
		}

		private void LateUpdate()
		{
			if (liveSpectateActive && !((Object)(object)liveCamera == (Object)null) && EnsureValidActiveTarget())
			{
				cameraFollower.Update(liveCamera, targetPlayer);
			}
		}

		internal void HandleLocalPlayerDeath(PlayerAvatar player)
		{
			LiveSpectateLog.LogLifecycle($"Local player death observed. active={liveSpectateActive}, isOwner={(Object)(object)owner == (Object)(object)player}.", $"HandleLocalPlayerDeath. playerExists={(Object)(object)player != (Object)null}, liveSpectateActive={liveSpectateActive}, isOwner={(Object)(object)owner == (Object)(object)player}.");
			if (liveSpectateActive && (Object)(object)owner == (Object)(object)player)
			{
				StopLiveSpectate();
			}
		}

		internal bool IsLiveSpectateOwner(PlayerAvatar player)
		{
			return liveSpectateActive && (Object)(object)owner == (Object)(object)player;
		}

		internal static bool IsLocalPlayer(PlayerAvatar player)
		{
			return LiveSpectatePlayerState.IsLocalPlayer(player);
		}

		internal void StopLiveSpectate()
		{
			LiveSpectateLog.LogLifecycle($"StopLiveSpectate begin. active={liveSpectateActive}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}.", $"StopLiveSpectate begin. active={liveSpectateActive}, ownerExists={(Object)(object)owner != (Object)null}, liveCameraExists={(Object)(object)liveCamera != (Object)null}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}.");
			bool flag = resourceRestorer.HasResources || (Object)(object)liveCameraObject != (Object)null || (Object)(object)liveCamera != (Object)null;
			if (!liveSpectateActive && !flag)
			{
				LiveSpectateLog.LogLifecycle("StopLiveSpectate skipped; inactive and no resources remain.", "StopLiveSpectate skipped because local live spectate is not active and no local resources remain.");
				return;
			}
			resourceRestorer.RestoreLightCullTarget(owner);
			LiveSpectateHudService.RestoreLiveSpectateHud();
			liveSpectateActive = false;
			owner = null;
			targetPlayer = null;
			cameraFollower.Reset();
			try
			{
				resourceRestorer.RestoreAudioListenerTarget(liveCamera);
			}
			catch (Exception arg)
			{
				LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate audio restore failed: {arg}");
			}
			try
			{
				resourceRestorer.RestoreOriginalCameras();
				resourceRestorer.RestoreCameraTags();
			}
			catch (Exception arg2)
			{
				LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate camera restore failed: {arg2}");
			}
			resourceRestorer.RestoreCameraUtilsMainCamera(liveCamera);
			try
			{
				if ((Object)(object)liveCameraObject != (Object)null)
				{
					Object.Destroy((Object)(object)liveCameraObject);
				}
			}
			catch (Exception arg3)
			{
				LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate local camera destroy failed: {arg3}");
			}
			finally
			{
				liveCameraObject = null;
				liveCamera = null;
			}
			LiveSpectateLog.LogLifecycle("StopLiveSpectate success. Local camera removed; originals restored.", "StopLiveSpectate success. Local-only camera destroyed; original cameras/audio targets restored. No player, CameraAim, or CameraPosition transforms were moved.");
		}

		private void TryStartLiveSpectate()
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			LiveSpectateLog.LogLifecycle("StartLiveSpectate begin.", "StartLiveSpectate begin (local-only renderer architecture). No SpectateCamera/SetSpectate/CameraPosition bridge will be used.");
			PlayerAvatar instance = PlayerAvatar.instance;
			if (!LiveSpectateInputPolicy.CanStart(instance, out var reason))
			{
				LiveSpectatePlugin.Log.LogWarning((object)("StartLiveSpectate denied. " + reason));
				return;
			}
			PlayerAvatar firstSpectateTarget = LiveSpectateTargets.GetFirstSpectateTarget(instance);
			if ((Object)(object)firstSpectateTarget == (Object)null)
			{
				LiveSpectatePlugin.Log.LogWarning((object)"StartLiveSpectate denied. Denied: no enabled non-local player target exists");
				return;
			}
			try
			{
				FreezeLocalControls();
				CreateLiveCamera();
				owner = instance;
				targetPlayer = firstSpectateTarget;
				resourceRestorer.CaptureOriginalLightCullTarget();
				resourceRestorer.SetLightCullTarget(firstSpectateTarget);
				Vector3 targetFocusPoint = LiveSpectateCameraFollower.GetTargetFocusPoint(firstSpectateTarget);
				Quaternion basis = (((Object)(object)GameDirector.instance.MainCamera != (Object)null) ? ((Component)GameDirector.instance.MainCamera).transform.rotation : Quaternion.LookRotation(((Component)instance).transform.forward, Vector3.up));
				cameraFollower.Initialize(liveCamera, basis, targetFocusPoint);
				resourceRestorer.MakeLiveCameraMainCamera(liveCamera);
				resourceRestorer.DisableOriginalCameras(GameDirector.instance.MainCamera);
				resourceRestorer.RedirectAudioListenerTarget(liveCamera);
				liveSpectateActive = true;
				bool flag = (Object)(object)((Component)liveCamera).GetComponent("PhotonView") != (Object)null;
				LiveSpectateLog.LogLifecycle("StartLiveSpectate success. target=" + LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget) + ".", $"StartLiveSpectate success. owner={LiveSpectateUtility.GetPlayerDisplayName(instance)}, target={LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget)}, liveCamera={((Object)liveCamera).name}, hasPhotonView={flag}, liveCameraTag={((Component)liveCamera).tag}.");
			}
			catch (Exception arg)
			{
				LiveSpectatePlugin.Log.LogWarning((object)$"Start local live spectate failed: {arg}");
				liveSpectateActive = true;
				StopLiveSpectate();
			}
		}

		private void CreateLiveCamera()
		{
			Camera mainCamera = GameDirector.instance.MainCamera;
			if ((Object)(object)mainCamera == (Object)null)
			{
				throw new InvalidOperationException("GameDirector.instance.MainCamera is null");
			}
			float liveFieldOfView = (((Object)(object)CameraZoom.Instance != (Object)null) ? CameraZoom.Instance.playerZoomDefault : mainCamera.fieldOfView);
			LiveSpectateCameraCloneResult liveSpectateCameraCloneResult = LiveSpectateCameraFactory.Create(mainCamera, liveFieldOfView);
			liveCameraObject = liveSpectateCameraCloneResult.RootObject;
			liveCamera = liveSpectateCameraCloneResult.RootCamera;
		}

		private void FreezeLocalControls()
		{
			PlayerController instance = PlayerController.instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.InputDisable(0.25f);
			}
		}

		private void HandleSpectateSwitchInput()
		{
			if (SemiFunc.InputDown((InputKey)23))
			{
				SwitchTarget(next: true);
			}
			else if (SemiFunc.InputDown((InputKey)24))
			{
				SwitchTarget(next: false);
			}
		}

		private void SwitchTarget(bool next)
		{
			List<PlayerAvatar> spectateTargets = LiveSpectateTargets.GetSpectateTargets(owner);
			if (spectateTargets.Count == 0)
			{
				LiveSpectatePlugin.Log.LogInfo((object)"Target switch found no enabled non-local players; stopping local live spectate.");
				StopLiveSpectate();
				return;
			}
			int num = spectateTargets.IndexOf(targetPlayer);
			int num2 = ((num >= 0) ? ((num + (next ? 1 : (-1)) + spectateTargets.Count) % spectateTargets.Count) : 0);
			targetPlayer = spectateTargets[num2];
			cameraFollower.ResetForTarget(targetPlayer, liveCamera, applyTransform: false);
			resourceRestorer.SetLightCullTarget(targetPlayer);
			LiveSpectateLog.LogLifecycle("Target switched to " + LiveSpectateUtility.GetPlayerDisplayName(targetPlayer) + ".", $"LiveSpectate target switched. next={next}, index={num2}/{spectateTargets.Count}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}.");
		}

		private bool EnsureValidActiveTarget()
		{
			if (LiveSpectateTargets.IsValidSpectateTarget(targetPlayer, owner))
			{
				return true;
			}
			PlayerAvatar player = targetPlayer;
			PlayerAvatar firstSpectateTarget = LiveSpectateTargets.GetFirstSpectateTarget(owner);
			if ((Object)(object)firstSpectateTarget == (Object)null)
			{
				LiveSpectatePlugin.Log.LogInfo((object)("Active target revalidation found no valid replacement after target became invalid (" + LiveSpectateUtility.GetPlayerDisplayName(player) + "); stopping local live spectate."));
				StopLiveSpectate();
				return false;
			}
			targetPlayer = firstSpectateTarget;
			cameraFollower.ResetForTarget(targetPlayer, liveCamera, applyTransform: false);
			resourceRestorer.SetLightCullTarget(targetPlayer);
			LiveSpectatePlugin.Log.LogInfo((object)("Active target revalidation retargeted local live spectate from " + LiveSpectateUtility.GetPlayerDisplayName(player) + " to " + LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget) + "."));
			return true;
		}

		private void LogKPressDiagnostic(string allowDenyReason)
		{
			try
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics == null || !verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate K pressed. active={liveSpectateActive}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}, decision={allowDenyReason}.");
					return;
				}
				PlayerAvatar instance = PlayerAvatar.instance;
				object memberValue = LiveSpectateReflection.GetMemberValue(MenuManager.instance, "currentMenuPage");
				object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("PlayerController", "instance");
				object obj = LiveSpectateReflection.GetMemberValue(staticMemberValue, "playerAvatarScript") ?? LiveSpectateReflection.GetMemberValue(staticMemberValue, "playerAvatar");
				LiveSpectateTargets.GetPlayerListCounts(instance, out var playerCount, out var enabledPlayerCount, out var enabledNonLocalCount);
				LiveSpectateInputPolicy.HasPlayableScene(out var reason);
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("LiveSpectate K press diagnostic:");
				stringBuilder.AppendLine($"  time={Time.unscaledTime:0.000}, frame={Time.frameCount}");
				stringBuilder.AppendLine($"  liveSpectateActive={liveSpectateActive}, liveCamera.exists={(Object)(object)liveCamera != (Object)null}, liveCameraObject.exists={(Object)(object)liveCameraObject != (Object)null}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}");
				object[] obj2 = new object[4]
				{
					(Object)(object)GameDirector.instance != (Object)null,
					null,
					null,
					null
				};
				GameDirector instance2 = GameDirector.instance;
				obj2[1] = ((instance2 != null) ? ((object)(gameState)(ref instance2.currentState)).ToString() : null) ?? "<null>";
				obj2[2] = (((Object)(object)GameDirector.instance != (Object)null) ? GameDirector.instance.DisableInput.ToString() : "<null>");
				obj2[3] = (Object)(object)GameDirector.instance?.MainCamera != (Object)null;
				stringBuilder.AppendLine(string.Format("  GameDirector.exists={0}, currentState={1}, DisableInput={2}, MainCamera.exists={3}", obj2));
				stringBuilder.AppendLine(string.Format("  LevelGenerator.exists={0}, Generated={1}", (Object)(object)LevelGenerator.Instance != (Object)null, ((Object)(object)LevelGenerator.Instance != (Object)null) ? LevelGenerator.Instance.Generated.ToString() : "<null>"));
				stringBuilder.AppendLine(string.Format("  sceneChecks: MenuLevel={0}, RunIsLobbyMenu={1}, RunIsLobby={2}, RunIsShop={3}, RunIsLevel={4}, RunIsArena={5}, RunIsTutorial={6}, playableReason={7}", LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.MenuLevel, "MenuLevel"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsLobbyMenu, "RunIsLobbyMenu"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsLobby, "RunIsLobby"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsShop, "RunIsShop"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsLevel, "RunIsLevel"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsArena, "RunIsArena"), LiveSpectateInputPolicy.SafeSceneBool((Func<bool>)SemiFunc.RunIsTutorial, "RunIsTutorial"), reason));
				stringBuilder.AppendLine(string.Format("  ChatManager.exists={0}, chatActive={1}", (Object)(object)ChatManager.instance != (Object)null, LiveSpectateReflection.GetBoolMember(ChatManager.instance, "chatActive")));
				stringBuilder.AppendLine($"  MenuManager.exists={(Object)(object)MenuManager.instance != (Object)null}, currentMenuPage={LiveSpectateUtility.DescribeObject(memberValue)}");
				stringBuilder.AppendLine($"  Keyboard.current.exists={Keyboard.current != null}");
				stringBuilder.AppendLine(string.Format("  PlayerAvatar.instance.exists={0}, name={1}, isLocal={2}, spectating={3}, isDisabled={4}, deadSet={5}, activeSelf={6}, spectatePointNull={7}", (Object)(object)instance != (Object)null, LiveSpectateUtility.GetPlayerDisplayName(instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.IsLocalField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.SpectatingField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.IsDisabledField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.DeadSetField, instance), ((Object)(object)instance != (Object)null) ? ((Component)instance).gameObject.activeSelf.ToString() : "<null>", (Object)(object)instance?.spectatePoint == (Object)null));
				stringBuilder.AppendLine($"  PlayerController.instance.exists={staticMemberValue != null}, playerAvatarScript.exists={obj != null}, equalsPlayerAvatar.instance={obj == instance}");
				stringBuilder.AppendLine("  Vanilla SpectateCamera is intentionally not inspected or used by LiveSpectate.");
				stringBuilder.AppendLine($"  CameraAim.Instance.exists={(Object)(object)CameraAim.Instance != (Object)null}, CameraPosition.instance.exists={(Object)(object)CameraPosition.instance != (Object)null} (diagnostic only; LiveSpectate does not move/use bridge)");
				stringBuilder.AppendLine($"  PlayerList.count={playerCount}, enabledPlayerCount={enabledPlayerCount}, enabledNonLocalTargetCount={enabledNonLocalCount}");
				stringBuilder.AppendLine("  decision=" + allowDenyReason);
				LiveSpectatePlugin.Log.LogInfo((object)stringBuilder.ToString().TrimEnd());
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate K press diagnostic failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	internal static class LiveSpectateHudService
	{
		internal static void UpdateLiveSpectateHud(PlayerAvatar targetPlayer)
		{
			try
			{
				SemiFunc.UIHideHealth();
				SemiFunc.UIHideOvercharge();
				SemiFunc.UIHideEnergy();
				SemiFunc.UIHideInventory();
				SemiFunc.UIHideAim();
				MissionUI instance = MissionUI.instance;
				if (instance != null)
				{
					((SemiUI)instance).Hide();
				}
				if ((Object)(object)targetPlayer != (Object)null)
				{
					SemiFunc.HUDSpectateSetName(LiveSpectateUtility.GetPlayerDisplayName(targetPlayer));
					SpectateNameUI instance2 = SpectateNameUI.instance;
					if (instance2 != null)
					{
						((SemiUI)instance2).Show();
					}
				}
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate HUD update failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}

		internal static void RestoreLiveSpectateHud()
		{
			try
			{
				SpectateNameUI instance = SpectateNameUI.instance;
				if (instance != null)
				{
					((SemiUI)instance).Hide();
				}
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate HUD restore failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}
	}
	internal static class LiveSpectateInputPolicy
	{
		internal static bool CanStart(PlayerAvatar player, out string reason)
		{
			if ((Object)(object)player == (Object)null)
			{
				reason = "Denied: PlayerAvatar.instance is null";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || !value)
			{
				reason = "Denied: PlayerAvatar.instance is not local or isLocal is unreadable";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2)
			{
				reason = "Denied: local player is disabled or isDisabled is unreadable";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3)
			{
				reason = "Denied: local player deadSet is true or unreadable";
				return false;
			}
			if (!CanToggleNow(out reason))
			{
				return false;
			}
			if ((Object)(object)LiveSpectateTargets.GetFirstSpectateTarget(player) == (Object)null)
			{
				reason = "Denied: no enabled non-local player target exists";
				return false;
			}
			reason = "Allowed: playable-scene, input, local-player, and non-local target checks passed";
			return true;
		}

		internal static bool CanContinueLiveSpectate(PlayerAvatar player, out string reason)
		{
			if ((Object)(object)player == (Object)null)
			{
				reason = "owner is null";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || !value)
			{
				reason = "owner is not local or isLocal is unreadable";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2)
			{
				reason = "owner is disabled or isDisabled is unreadable";
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3)
			{
				reason = "owner deadSet is true or unreadable";
				return false;
			}
			if (!HasPlayableScene(out reason))
			{
				return false;
			}
			if ((Object)(object)LiveSpectateTargets.GetFirstSpectateTarget(player) == (Object)null)
			{
				reason = "no enabled non-local player target exists";
				return false;
			}
			reason = "continuation allowed";
			return true;
		}

		internal static bool CanToggleNow(out string reason)
		{
			GameDirector instance = GameDirector.instance;
			if ((Object)(object)instance == (Object)null)
			{
				reason = "Denied: GameDirector.instance is null";
				return false;
			}
			if (instance.DisableInput)
			{
				reason = "Denied: GameDirector.DisableInput is true";
				return false;
			}
			if (LiveSpectateReflection.GetBoolMember(ChatManager.instance, "chatActive"))
			{
				reason = "Denied: ChatManager.chatActive is true";
				return false;
			}
			if (!HasPlayableScene(out reason))
			{
				reason = "Denied: " + reason;
				return false;
			}
			reason = "Allowed: playable scene and no blocking chat/input state";
			return true;
		}

		internal static bool HasPlayableScene(out string reason)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			GameDirector instance = GameDirector.instance;
			if ((Object)(object)instance == (Object)null)
			{
				reason = "GameDirector.instance is null";
				return false;
			}
			if ((int)instance.currentState != 2)
			{
				reason = $"GameDirector.currentState is {instance.currentState}, expected Main";
				return false;
			}
			if ((Object)(object)LevelGenerator.Instance == (Object)null)
			{
				reason = "LevelGenerator.Instance is null";
				return false;
			}
			if (!LevelGenerator.Instance.Generated)
			{
				reason = "LevelGenerator.Instance.Generated is false";
				return false;
			}
			bool flag = SafeSceneBool((Func<bool>)SemiFunc.MenuLevel, "MenuLevel");
			bool flag2 = SafeSceneBool((Func<bool>)SemiFunc.RunIsLobbyMenu, "RunIsLobbyMenu");
			bool flag3 = SafeSceneBool((Func<bool>)SemiFunc.RunIsLobby, "RunIsLobby");
			bool flag4 = SafeSceneBool((Func<bool>)SemiFunc.RunIsShop, "RunIsShop");
			if (flag || flag2 || flag3 || flag4)
			{
				reason = $"blocked scene state: MenuLevel={flag}, RunIsLobbyMenu={flag2}, RunIsLobby={flag3}, RunIsShop={flag4}";
				return false;
			}
			bool flag5 = SafeSceneBool((Func<bool>)SemiFunc.RunIsLevel, "RunIsLevel");
			bool flag6 = SafeSceneBool((Func<bool>)SemiFunc.RunIsArena, "RunIsArena");
			bool flag7 = SafeSceneBool((Func<bool>)SemiFunc.RunIsTutorial, "RunIsTutorial");
			if (!flag5 && !flag6 && !flag7)
			{
				reason = $"not a playable scene: RunIsLevel={flag5}, RunIsArena={flag6}, RunIsTutorial={flag7}";
				return false;
			}
			reason = $"playable scene confirmed: Generated=true, RunIsLevel={flag5}, RunIsArena={flag6}, RunIsTutorial={flag7}";
			return true;
		}

		internal static bool SafeSceneBool(Func<bool> getter, string name)
		{
			try
			{
				return getter();
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("Scene check " + name + " failed: " + ex.GetType().Name + ": " + ex.Message));
				}
				return false;
			}
		}
	}
	internal static class LiveSpectateLog
	{
		internal static void LogLifecycle(string compactMessage, string verboseMessage)
		{
			ManualLogSource log = LiveSpectatePlugin.Log;
			ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
			log.LogInfo((object)((verboseDiagnostics != null && verboseDiagnostics.Value) ? verboseMessage : compactMessage));
		}
	}
	internal static class LiveSpectateMath
	{
		internal static bool IsFinite(Vector3 value)
		{
			//IL_0001: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			return IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z);
		}

		internal static bool IsFinite(Quaternion value)
		{
			//IL_0001: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			return IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z) && IsFinite(value.w);
		}

		internal static bool IsFinite(float value)
		{
			return !float.IsNaN(value) && !float.IsInfinity(value);
		}
	}
	internal static class LiveSpectatePlayerState
	{
		internal static readonly FieldInfo IsLocalField = GetPlayerField("isLocal");

		internal static readonly FieldInfo IsDisabledField = GetPlayerField("isDisabled");

		internal static readonly FieldInfo SpectatingField = GetPlayerField("spectating");

		internal static readonly FieldInfo DeadSetField = GetPlayerField("deadSet");

		internal static bool IsLocalPlayer(PlayerAvatar player)
		{
			bool value = default(bool);
			return (Object)(object)player != (Object)null && TryGetBool(IsLocalField, player, out value) && value;
		}

		internal static bool GetBool(FieldInfo field, PlayerAvatar player)
		{
			bool value;
			return TryGetBool(field, player, out value) && value;
		}

		internal static bool TryGetBool(FieldInfo field, PlayerAvatar player, out bool value)
		{
			value = false;
			if (field == null || (Object)(object)player == (Object)null)
			{
				return false;
			}
			try
			{
				object value2 = field.GetValue(player);
				if (value2 is bool)
				{
					bool flag = (bool)value2;
					if (true)
					{
						value = flag;
						return true;
					}
				}
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("PlayerAvatar bool field read failed for " + field.Name + ": " + ex.GetType().Name + ": " + ex.Message));
				}
			}
			return false;
		}

		private static FieldInfo GetPlayerField(string fieldName)
		{
			FieldInfo field = typeof(PlayerAvatar).GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null)
			{
				ManualLogSource log = LiveSpectatePlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("PlayerAvatar." + fieldName + " field was not found."));
				}
			}
			return field;
		}
	}
	[BepInPlugin("com.zhuanban.repo.livespectate", "LiveSpectate", "1.0.0")]
	public sealed class LiveSpectatePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.zhuanban.repo.livespectate";

		public const string PluginName = "LiveSpectate";

		public const string PluginVersion = "1.0.0";

		private Harmony harmony;

		internal static ManualLogSource Log { get; private set; }

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

		private void Awake()
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			VerboseDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseDiagnostics", false, "Enable extra LiveSpectate diagnostic logging. Default logging stays compact.");
			Log.LogInfo((object)string.Format("{0} awake. Version={1}, verboseDiagnostics={2}.", "LiveSpectate", "1.0.0", VerboseDiagnostics.Value));
			LiveSpectateController instance = ((Component)this).gameObject.AddComponent<LiveSpectateController>();
			LiveSpectateController.Instance = instance;
			Log.LogInfo((object)"LiveSpectateController attached.");
			harmony = new Harmony("com.zhuanban.repo.livespectate");
			harmony.PatchAll();
			Log.LogInfo((object)"Harmony patches applied.");
			Log.LogInfo((object)"LiveSpectate 1.0.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)LiveSpectateController.Instance != (Object)null)
			{
				LiveSpectateController.Instance.StopLiveSpectate();
				LiveSpectateController.Instance = null;
			}
		}
	}
	internal static class LiveSpectateReflection
	{
		internal static bool GetBoolMember(object target, string memberName)
		{
			object memberValue = GetMemberValue(target, memberName);
			bool flag = default(bool);
			int num;
			if (memberValue is bool)
			{
				flag = (bool)memberValue;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		internal static float GetFloatMember(object target, string memberName, float fallback)
		{
			object memberValue = GetMemberValue(target, memberName);
			if (memberValue is float result)
			{
				return result;
			}
			if (memberValue is int num)
			{
				return num;
			}
			return fallback;
		}

		internal static object GetMemberValue(object target, string memberName)
		{
			if (target == null)
			{
				return null;
			}
			try
			{
				Type type = target.GetType();
				while (type != null)
				{
					FieldInfo field = type.GetField(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (field != null)
					{
						return field.GetValue(field.IsStatic ? null : target);
					}
					PropertyInfo property = type.GetProperty(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (property != null && property.GetIndexParameters().Length == 0)
					{
						return property.GetValue((property.GetMethod != null && property.GetMethod.IsStatic) ? null : target);
					}
					type = type.BaseType;
				}
				return null;
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("Reflection read failed for " + memberName + ": " + ex.GetType().Name));
				}
				return null;
			}
		}

		internal static bool TrySetMemberValue(object target, string memberName, object value)
		{
			if (target == null)
			{
				return false;
			}
			try
			{
				Type type = target.GetType();
				while (type != null)
				{
					FieldInfo field = type.GetField(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (field != null)
					{
						field.SetValue(field.IsStatic ? null : target, value);
						return true;
					}
					PropertyInfo property = type.GetProperty(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (property != null && property.GetIndexParameters().Length == 0 && property.SetMethod != null)
					{
						property.SetValue(property.SetMethod.IsStatic ? null : target, value);
						return true;
					}
					type = type.BaseType;
				}
				return false;
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("Reflection write failed for " + memberName + ": " + ex.GetType().Name));
				}
				return false;
			}
		}

		internal static object GetStaticMemberValue(string typeName, string memberName)
		{
			try
			{
				Type type = FindType(typeName);
				if (type == null)
				{
					return null;
				}
				FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field.GetValue(null);
				}
				PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property == null || property.GetIndexParameters().Length != 0)
				{
					return null;
				}
				return property.GetValue(null);
			}
			catch (Exception ex)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics != null && verboseDiagnostics.Value)
				{
					LiveSpectatePlugin.Log.LogWarning((object)("Static reflection read failed for " + typeName + "." + memberName + ": " + ex.GetType().Name));
				}
				return null;
			}
		}

		internal static Type FindType(string typeName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type type = assembly.GetType(typeName);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}
	}
	internal sealed class LiveSpectateResourceRestorer
	{
		private readonly struct CameraState
		{
			internal Camera Camera { get; }

			internal bool OriginalEnabled { get; }

			internal bool AssignedEnabled { get; }

			internal CameraState(Camera camera, bool originalEnabled, bool assignedEnabled)
			{
				Camera = camera;
				OriginalEnabled = originalEnabled;
				AssignedEnabled = assignedEnabled;
			}
		}

		private readonly struct CameraTagState
		{
			internal Camera Camera { get; }

			internal string OriginalTag { get; }

			internal string AssignedTag { get; }

			internal CameraTagState(Camera camera, string originalTag, string assignedTag)
			{
				Camera = camera;
				OriginalTag = originalTag;
				AssignedTag = assignedTag;
			}
		}

		private const string MainCameraTag = "MainCamera";

		private CameraState[] disabledCameraStates;

		private CameraTagState[] originalCameraTagStates;

		private Camera originalCameraUtilsMainCamera;

		private bool originalCameraUtilsMainCameraCaptured;

		private Transform originalAudioTargetPosition;

		private Transform originalAudioTargetRotation;

		private Transform originalLightCullTarget;

		private bool originalLightCullTargetCaptured;

		private Transform liveSpectateLightCullTarget;

		internal bool HasResources => disabledCameraStates != null || originalCameraTagStates != null || originalCameraUtilsMainCameraCaptured || (Object)(object)originalAudioTargetPosition != (Object)null || (Object)(object)originalAudioTargetRotation != (Object)null || originalLightCullTargetCaptured || (Object)(object)liveSpectateLightCullTarget != (Object)null;

		internal void DisableOriginalCameras(Camera mainCamera)
		{
			Camera[] componentsInChildren = ((Component)mainCamera).GetComponentsInChildren<Camera>(true);
			disabledCameraStates = new CameraState[componentsInChildren.Length];
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				bool enabled = ((Behaviour)componentsInChildren[i]).enabled;
				disabledCameraStates[i] = new CameraState(componentsInChildren[i], enabled, assignedEnabled: false);
				((Behaviour)componentsInChildren[i]).enabled = false;
			}
			LiveSpectateLog.LogLifecycle($"Disabled {componentsInChildren.Length} original camera(s).", $"Disabled {componentsInChildren.Length} original main-camera hierarchy cameras for local live spectate; stored enabled states for restore.");
		}

		internal void MakeLiveCameraMainCamera(Camera liveCamera)
		{
			if ((Object)(object)liveCamera == (Object)null)
			{
				return;
			}
			try
			{
				List<CameraTagState> list = new List<CameraTagState>();
				originalCameraTagStates = list.ToArray();
				Camera[] allCameras = Camera.allCameras;
				Camera[] array = allCameras;
				foreach (Camera val in array)
				{
					if (!((Object)(object)val == (Object)null))
					{
						string text = LiveSpectateUtility.SafeGetTag(((Component)val).gameObject);
						bool flag = (Object)(object)val == (Object)(object)liveCamera;
						if (flag ? (text != "MainCamera") : (text == "MainCamera"))
						{
							string text2 = (flag ? "MainCamera" : "Untagged");
							list.Add(new CameraTagState(val, text, text2));
							originalCameraTagStates = list.ToArray();
							((Component)val).gameObject.tag = text2;
						}
					}
				}
				if (LiveSpectateUtility.SafeGetTag(((Component)liveCamera).gameObject) != "MainCamera")
				{
					list.Add(new CameraTagState(liveCamera, LiveSpectateUtility.SafeGetTag(((Component)liveCamera).gameObject), "MainCamera"));
					originalCameraTagStates = list.ToArray();
					((Component)liveCamera).gameObject.tag = "MainCamera";
				}
				originalCameraTagStates = list.ToArray();
				LiveSpectateLog.LogLifecycle("Live camera tagged MainCamera for compatibility.", $"Live camera tagged MainCamera for compatibility. changedTags={originalCameraTagStates.Length}.");
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate MainCamera tag handoff failed: " + ex.GetType().Name + ": " + ex.Message));
			}
			CaptureAndSetCameraUtilsMainCamera(liveCamera);
		}

		internal void RestoreOriginalCameras()
		{
			if (disabledCameraStates == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			CameraState[] array = disabledCameraStates;
			for (int i = 0; i < array.Length; i++)
			{
				CameraState cameraState = array[i];
				try
				{
					if ((Object)(object)cameraState.Camera != (Object)null)
					{
						if (((Behaviour)cameraState.Camera).enabled != cameraState.AssignedEnabled)
						{
							num3++;
							continue;
						}
						((Behaviour)cameraState.Camera).enabled = cameraState.OriginalEnabled;
						num++;
					}
				}
				catch (Exception ex)
				{
					num2++;
					LiveSpectatePlugin.Log.LogWarning((object)("Failed to restore original camera enabled state: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
			LiveSpectateLog.LogLifecycle($"Restored {num}/{disabledCameraStates.Length} original camera enabled state(s). skipped={num3}.", $"Restored {num}/{disabledCameraStates.Length} original camera enabled states. failed={num2}, skippedOwnershipChanged={num3}.");
			disabledCameraStates = null;
		}

		internal void RestoreCameraTags()
		{
			if (originalCameraTagStates == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			CameraTagState[] array = originalCameraTagStates;
			for (int i = 0; i < array.Length; i++)
			{
				CameraTagState cameraTagState = array[i];
				try
				{
					if ((Object)(object)cameraTagState.Camera != (Object)null)
					{
						string text = LiveSpectateUtility.SafeGetTag(((Component)cameraTagState.Camera).gameObject);
						if (text != cameraTagState.AssignedTag)
						{
							num3++;
							continue;
						}
						((Component)cameraTagState.Camera).gameObject.tag = cameraTagState.OriginalTag;
						num++;
					}
				}
				catch (Exception ex)
				{
					num2++;
					LiveSpectatePlugin.Log.LogWarning((object)("Failed to restore camera tag: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
			LiveSpectateLog.LogLifecycle($"Restored {num}/{originalCameraTagStates.Length} camera tag(s). skipped={num3}.", $"Restored {num}/{originalCameraTagStates.Length} camera tags. failed={num2}, skippedOwnershipChanged={num3}.");
			originalCameraTagStates = null;
		}

		internal void RestoreCameraUtilsMainCamera(Camera liveCamera)
		{
			if (!originalCameraUtilsMainCameraCaptured)
			{
				return;
			}
			try
			{
				object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("CameraUtils", "Instance");
				Camera val = (Camera)((staticMemberValue != null) ? /*isinst with value type is only supported in some contexts*/: null);
				if (staticMemberValue != null && (Object)(object)val == (Object)(object)liveCamera && LiveSpectateReflection.TrySetMemberValue(staticMemberValue, "MainCamera", originalCameraUtilsMainCamera))
				{
					LiveSpectateLog.LogLifecycle("CameraUtils MainCamera restored.", "CameraUtils.Instance.MainCamera restored to its captured original camera.");
				}
				else if (staticMemberValue != null)
				{
					LiveSpectateLog.LogLifecycle("CameraUtils MainCamera restore skipped; current camera is no longer live camera.", "CameraUtils.Instance.MainCamera restore skipped because current=" + LiveSpectateUtility.DescribeCamera(val) + " is not the LiveSpectate-owned live camera=" + LiveSpectateUtility.DescribeCamera(liveCamera) + ".");
				}
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate CameraUtils MainCamera restore failed: " + ex.GetType().Name + ": " + ex.Message));
			}
			finally
			{
				originalCameraUtilsMainCamera = null;
				originalCameraUtilsMainCameraCaptured = false;
			}
		}

		internal void RedirectAudioListenerTarget(Camera liveCamera)
		{
			AudioListenerFollow instance = AudioListenerFollow.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)liveCamera == (Object)null)
			{
				LiveSpectatePlugin.Log.LogInfo((object)$"Audio listener target redirect skipped. AudioListenerFollow.exists={(Object)(object)instance != (Object)null}, liveCamera.exists={(Object)(object)liveCamera != (Object)null}.");
				return;
			}
			originalAudioTargetPosition = instance.TargetPositionTransform;
			originalAudioTargetRotation = instance.TargetRotationTransform;
			instance.TargetPositionTransform = ((Component)liveCamera).transform;
			instance.TargetRotationTransform = ((Component)liveCamera).transform;
			LiveSpectatePlugin.Log.LogInfo((object)"AudioListenerFollow target redirected to local live camera.");
		}

		internal void RestoreAudioListenerTarget(Camera liveCamera)
		{
			AudioListenerFollow instance = AudioListenerFollow.instance;
			if ((Object)(object)instance == (Object)null)
			{
				originalAudioTargetPosition = null;
				originalAudioTargetRotation = null;
				return;
			}
			Transform val = ((liveCamera != null) ? ((Component)liveCamera).transform : null);
			GameDirector instance2 = GameDirector.instance;
			object obj;
			if (instance2 == null)
			{
				obj = null;
			}
			else
			{
				Camera mainCamera = instance2.MainCamera;
				obj = ((mainCamera != null) ? ((Component)mainCamera).transform : null);
			}
			Transform val2 = (Transform)obj;
			int num = 0;
			int num2 = 0;
			if ((Object)(object)instance.TargetPositionTransform == (Object)null || (Object)(object)instance.TargetPositionTransform == (Object)(object)val)
			{
				instance.TargetPositionTransform = (((Object)(object)originalAudioTargetPosition != (Object)null) ? originalAudioTargetPosition : val2);
				num++;
			}
			else
			{
				num2++;
			}
			if ((Object)(object)instance.TargetRotationTransform == (Object)null || (Object)(object)instance.TargetRotationTransform == (Object)(object)val)
			{
				instance.TargetRotationTransform = (((Object)(object)originalAudioTargetRotation != (Object)null) ? originalAudioTargetRotation : val2);
				num++;
			}
			else
			{
				num2++;
			}
			originalAudioTargetPosition = null;
			originalAudioTargetRotation = null;
			LiveSpectatePlugin.Log.LogInfo((object)$"AudioListenerFollow target restore complete. restored={num}, skipped={num2}.");
		}

		internal void CaptureOriginalLightCullTarget()
		{
			if (originalLightCullTargetCaptured)
			{
				return;
			}
			try
			{
				originalLightCullTarget = GetCurrentLightCullTarget();
				originalLightCullTargetCaptured = true;
				LiveSpectateLog.LogLifecycle("Captured original LightManager cull target.", "Captured original LightManager cull target: " + LiveSpectateUtility.DescribeTransform(originalLightCullTarget) + ".");
			}
			catch (Exception ex)
			{
				originalLightCullTarget = null;
				originalLightCullTargetCaptured = true;
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target capture failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal void SetLightCullTarget(PlayerAvatar player)
		{
			Transform val = (((Object)(object)player != (Object)null) ? ((Component)player).transform : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				SemiFunc.LightManagerSetCullTargetTransform(val);
				liveSpectateLightCullTarget = val;
				LightManager instance = LightManager.instance;
				if (instance != null)
				{
					instance.UpdateInstant();
				}
				LiveSpectateLog.LogLifecycle("Light cull target set to " + LiveSpectateUtility.GetPlayerDisplayName(player) + ".", "LightManager cull target set to live spectate target " + LiveSpectateUtility.GetPlayerDisplayName(player) + " and updated instantly.");
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target set failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal void RestoreLightCullTarget(PlayerAvatar owner)
		{
			bool flag = originalLightCullTargetCaptured && (Object)(object)originalLightCullTarget != (Object)null;
			object obj;
			if (!flag)
			{
				if (!((Object)(object)owner != (Object)null))
				{
					PlayerAvatar instance = PlayerAvatar.instance;
					obj = ((instance != null) ? ((Component)instance).transform : null);
				}
				else
				{
					obj = ((Component)owner).transform;
				}
			}
			else
			{
				obj = originalLightCullTarget;
			}
			Transform val = (Transform)obj;
			if ((Object)(object)val == (Object)null)
			{
				originalLightCullTarget = null;
				originalLightCullTargetCaptured = false;
				liveSpectateLightCullTarget = null;
				return;
			}
			try
			{
				Transform currentLightCullTarget = GetCurrentLightCullTarget();
				if ((Object)(object)currentLightCullTarget == (Object)null || (Object)(object)currentLightCullTarget == (Object)(object)liveSpectateLightCullTarget)
				{
					SemiFunc.LightManagerSetCullTargetTransform(val);
					LightManager instance2 = LightManager.instance;
					if (instance2 != null)
					{
						instance2.UpdateInstant();
					}
					LiveSpectateLog.LogLifecycle("Light cull target restored.", "LightManager cull target restored to " + (flag ? LiveSpectateUtility.DescribeTransform(val) : ("fallback local/owner player " + LiveSpectateUtility.GetPlayerDisplayName(owner ?? PlayerAvatar.instance))) + " and updated instantly.");
				}
				else
				{
					LiveSpectateLog.LogLifecycle("Light cull target restore skipped; current target changed.", "LightManager cull target restore skipped because current=" + LiveSpectateUtility.DescribeTransform(currentLightCullTarget) + " is not LiveSpectate-owned target=" + LiveSpectateUtility.DescribeTransform(liveSpectateLightCullTarget) + ".");
				}
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target restore failed: " + ex.GetType().Name + ": " + ex.Message));
			}
			finally
			{
				originalLightCullTarget = null;
				originalLightCullTargetCaptured = false;
				liveSpectateLightCullTarget = null;
			}
		}

		private void CaptureAndSetCameraUtilsMainCamera(Camera liveCamera)
		{
			if ((Object)(object)liveCamera == (Object)null)
			{
				return;
			}
			try
			{
				object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("CameraUtils", "Instance");
				if (staticMemberValue != null)
				{
					if (!originalCameraUtilsMainCameraCaptured)
					{
						ref Camera reference = ref originalCameraUtilsMainCamera;
						object memberValue = LiveSpectateReflection.GetMemberValue(staticMemberValue, "MainCamera");
						reference = (Camera)((memberValue is Camera) ? memberValue : null);
						originalCameraUtilsMainCameraCaptured = true;
					}
					if (LiveSpectateReflection.TrySetMemberValue(staticMemberValue, "MainCamera", liveCamera))
					{
						LiveSpectateLog.LogLifecycle("CameraUtils MainCamera redirected to live camera.", "CameraUtils.Instance.MainCamera redirected to the local live spectate camera for vanilla utility visual parity.");
					}
				}
			}
			catch (Exception ex)
			{
				LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate CameraUtils MainCamera redirect failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static Transform GetCurrentLightCullTarget()
		{
			LightManager instance = LightManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			object memberValue = LiveSpectateReflection.GetMemberValue(instance, "lightCullTarget");
			return (Transform)((memberValue is Transform) ? memberValue : null);
		}
	}
	internal static class LiveSpectateTargets
	{
		internal static PlayerAvatar GetFirstSpectateTarget(PlayerAvatar localOwner)
		{
			List<PlayerAvatar> spectateTargets = GetSpectateTargets(localOwner);
			return (spectateTargets.Count > 0) ? spectateTargets[0] : null;
		}

		internal static List<PlayerAvatar> GetSpectateTargets(PlayerAvatar localOwner)
		{
			List<PlayerAvatar> list = new List<PlayerAvatar>();
			GameDirector instance = GameDirector.instance;
			if (instance?.PlayerList == null)
			{
				return list;
			}
			foreach (PlayerAvatar player in instance.PlayerList)
			{
				if (IsValidSpectateTarget(player, localOwner))
				{
					list.Add(player);
				}
			}
			return list;
		}

		internal static bool IsValidSpectateTarget(PlayerAvatar player, PlayerAvatar localOwner)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player == (Object)(object)localOwner || !((Component)player).gameObject.activeInHierarchy)
			{
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || value)
			{
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2)
			{
				return false;
			}
			if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3)
			{
				return false;
			}
			return true;
		}

		internal static void GetPlayerListCounts(PlayerAvatar localOwner, out int playerCount, out int enabledPlayerCount, out int enabledNonLocalCount)
		{
			playerCount = -1;
			enabledPlayerCount = -1;
			enabledNonLocalCount = -1;
			GameDirector instance = GameDirector.instance;
			if (instance?.PlayerList == null)
			{
				return;
			}
			playerCount = 0;
			enabledPlayerCount = 0;
			enabledNonLocalCount = 0;
			foreach (PlayerAvatar player in instance.PlayerList)
			{
				playerCount++;
				if ((Object)(object)player != (Object)null && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value) && !value && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value2) && !value2)
				{
					enabledPlayerCount++;
					if ((Object)(object)player != (Object)(object)localOwner && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value3) && !value3)
					{
						enabledNonLocalCount++;
					}
				}
			}
		}
	}
	internal static class LiveSpectateUtility
	{
		internal const string UntaggedTag = "Untagged";

		internal static string GetPlayerDisplayName(PlayerAvatar player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return "<null>";
			}
			return (LiveSpectateReflection.GetMemberValue(player, "playerName") ?? LiveSpectateReflection.GetMemberValue(player, "name"))?.ToString() ?? ((Object)player).name;
		}

		internal static string DescribeObject(object value)
		{
			if (value == null)
			{
				return "<null>";
			}
			string text;
			try
			{
				text = value.ToString();
			}
			catch (Exception ex)
			{
				text = "<ToString failed: " + ex.GetType().Name + ">";
			}
			Object val = (Object)((value is Object) ? value : null);
			string text2 = ((val == null) ? (LiveSpectateReflection.GetMemberValue(value, "name") as string) : ((val != (Object)null) ? val.name : "<destroyed>"));
			string fullName = value.GetType().FullName;
			return "value=" + text + ", name=" + (text2 ?? "<none>") + ", type=" + fullName;
		}

		internal static string DescribeCamera(Camera camera)
		{
			return ((Object)(object)camera != (Object)null) ? (((Object)camera).name + " (" + ((object)camera).GetType().FullName + ")") : "<null>";
		}

		internal static string DescribeTransform(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return "<null>";
			}
			return ((Object)transform).name + " (" + ((object)transform).GetType().FullName + ")";
		}

		internal static string SafeGetTag(GameObject gameObject)
		{
			try
			{
				return ((Object)(object)gameObject != (Object)null) ? gameObject.tag : "Untagged";
			}
			catch
			{
				return "Untagged";
			}
		}
	}
}
namespace LiveSpectate.Patches
{
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
	internal static class PlayerAvatarDeathPatch
	{
		private static void Prefix(PlayerAvatar __instance)
		{
			bool flag = LiveSpectateController.IsLocalPlayer(__instance);
			bool flag2 = flag && (Object)(object)LiveSpectateController.Instance != (Object)null && LiveSpectateController.Instance.IsLiveSpectateOwner(__instance);
			if (!flag)
			{
				ConfigEntry<bool> verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics;
				if (verboseDiagnostics == null || !verboseDiagnostics.Value)
				{
					goto IL_0071;
				}
			}
			LiveSpectatePlugin.Log.LogInfo((object)$"PlayerDeathRPC prefix. playerExists={(Object)(object)__instance != (Object)null}, isLocal={flag}, willStopLiveSpectate={flag2}, skipOriginal=false.");
			goto IL_0071;
			IL_0071:
			if (flag)
			{
				LiveSpectateController.Instance?.HandleLocalPlayerDeath(__instance);
			}
		}
	}
}