Decompiled source of AutoCameraSpectator v1.0.5

BepInEx/plugins/AutoCameraSpectator/AutoCameraSpectator.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TactiKot")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Automatic PEAK spectator camera with scene-aware player selection and event framing.")]
[assembly: AssemblyFileVersion("1.0.5.0")]
[assembly: AssemblyInformationalVersion("1.0.5")]
[assembly: AssemblyProduct("AutoCameraSpectator")]
[assembly: AssemblyTitle("AutoCameraSpectator")]
[assembly: AssemblyVersion("1.0.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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 AutoCameraSpectator
{
	internal static class AutoTargetSelector
	{
		private readonly struct TargetCandidate
		{
			internal PlayerSnapshot Player { get; }

			internal SceneScore Breakdown { get; }

			internal float Score => Breakdown.Final;

			internal TargetCandidate(PlayerSnapshot player, SceneScore breakdown)
			{
				Player = player;
				Breakdown = breakdown;
			}
		}

		private sealed class TargetHistory
		{
			internal float LastSeenTime { get; set; }

			internal int LastThreatCount { get; set; }

			internal float LastThreatDistance { get; set; } = float.PositiveInfinity;

			internal bool WasFalling { get; set; }

			internal bool WasPassedOut { get; set; }

			internal float LastWatchedTime { get; set; } = -9999f;

			internal bool HasSmoothedScore { get; set; }

			internal float SmoothedScore { get; set; }

			internal float LastScoreTime { get; set; }
		}

		private const float RecentHistorySeconds = 2f;

		private const float LongWatchFadeSeconds = 8f;

		private const float RecentlyWatchedPenalty = 15f;

		private const float RecentlyWatchedPenaltySeconds = 12f;

		private const float HistoryPruneSeconds = 30f;

		private const float ScoreRiseSeconds = 0.45f;

		private const float ScoreFallSeconds = 1.1f;

		private static readonly Dictionary<int, TargetHistory> Histories = new Dictionary<int, TargetHistory>();

		private static readonly List<TargetCandidate> Candidates = new List<TargetCandidate>();

		private static int lastTelemetryVersion = int.MinValue;

		private static int currentTargetId;

		private static float watchStartedTime;

		private static float lastSwitchTime = -9999f;

		internal static bool Active { get; private set; }

		internal static void ToggleByUser()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			if (!SpectatorSession.CanSelectOthers)
			{
				Plugin.Instance.ShowStatus("Player switching is available after death", Color.yellow);
			}
			else if (!Plugin.Instance.TargetSelectionEnabled.Value)
			{
				SetActive(active: false, showMessage: false, "config disabled");
				Plugin.Instance.ShowStatus("Auto player selection disabled in config", Color.yellow);
			}
			else
			{
				SetActive(!Active, showMessage: true, "user toggle");
			}
		}

		internal static void OnManualSwitch()
		{
			if (Active && Plugin.SpectatorControlsAllowed())
			{
				SetActive(active: false, showMessage: true, "manual target switch");
			}
		}

		internal static void Apply()
		{
			if (!CanRun())
			{
				ResetSelectionState();
			}
			else
			{
				if (SpectatorTelemetry.Version == lastTelemetryVersion)
				{
					return;
				}
				lastTelemetryVersion = SpectatorTelemetry.Version;
				float unscaledTime = Time.unscaledTime;
				SyncCurrentWatch((SpectatorTelemetry.TryGet(MainCameraMovement.specCharacter, out PlayerSnapshot snapshot) ? snapshot : null)?.StableId ?? 0, unscaledTime);
				IReadOnlyList<PlayerSnapshot> players = SpectatorTelemetry.Players;
				List<TargetCandidate> candidates = Candidates;
				candidates.Clear();
				TargetCandidate? targetCandidate = null;
				TargetCandidate? targetCandidate2 = null;
				for (int i = 0; i < players.Count; i++)
				{
					PlayerSnapshot playerSnapshot = players[i];
					if (playerSnapshot.IsSelectable && SpectatorTelemetry.IsSelectableNow(playerSnapshot.Character))
					{
						TargetCandidate targetCandidate3 = CreateCandidate(playerSnapshot, players, currentTargetId, unscaledTime);
						candidates.Add(targetCandidate3);
						if (targetCandidate3.Player.StableId == currentTargetId)
						{
							targetCandidate = targetCandidate3;
						}
						if (!targetCandidate2.HasValue || targetCandidate3.Score > targetCandidate2.Value.Score)
						{
							targetCandidate2 = targetCandidate3;
						}
					}
				}
				if (targetCandidate2.HasValue && ShouldSwitch(targetCandidate, targetCandidate2.Value, unscaledTime))
				{
					SwitchTo(targetCandidate2.Value, targetCandidate, candidates, unscaledTime);
				}
				UpdateHistories(players, unscaledTime);
				PruneHistories(unscaledTime);
			}
		}

		internal static void Reset()
		{
			Active = false;
			EndSession();
		}

		internal static void EndSession()
		{
			Histories.Clear();
			Candidates.Clear();
			lastSwitchTime = -9999f;
			ResetSelectionState();
		}

		private static bool CanRun()
		{
			if (Active && Plugin.Instance.TargetSelectionEnabled.Value && SpectatorSession.CanSelectOthers)
			{
				return Plugin.SpectatorControlsAllowed();
			}
			return false;
		}

		private static TargetCandidate CreateCandidate(PlayerSnapshot player, IReadOnlyList<PlayerSnapshot> players, int currentId, float now)
		{
			TargetHistory history = GetHistory(player.StableId);
			SceneSignals signals = GetSignals(player, history, now);
			SceneScore sceneScore = SceneEvaluator.Evaluate(player, players, signals);
			float smoothedScene = SmoothScore(history, sceneScore.Scene, signals, now);
			float currentBonus = ((player.StableId == currentId) ? GetCurrentTargetBonus(now) : 0f);
			float recentlyWatchedPenalty = ((player.StableId == currentId) ? 0f : (GetRecentlyWatchedPenalty(history, now) * 15f));
			return new TargetCandidate(player, sceneScore.WithFinal(smoothedScene, currentBonus, recentlyWatchedPenalty));
		}

		private static SceneSignals GetSignals(PlayerSnapshot player, TargetHistory history, float now)
		{
			int num;
			int num2;
			if (history.LastSeenTime > 0f)
			{
				num = ((now - history.LastSeenTime <= 2f) ? 1 : 0);
				if (num != 0 && history.LastThreatCount == 0)
				{
					num2 = ((player.Threats.Count > 0) ? 1 : 0);
					goto IL_003e;
				}
			}
			else
			{
				num = 0;
			}
			num2 = 0;
			goto IL_003e;
			IL_003e:
			bool threatAppeared = (byte)num2 != 0;
			bool fallStarted = num != 0 && !history.WasFalling && player.IsFalling;
			bool passedOutStarted = num != 0 && !history.WasPassedOut && player.IsPassedOut;
			float threatClosingIn = 0f;
			if (num != 0 && IsFinite(history.LastThreatDistance) && IsFinite(player.NearestThreatDistance))
			{
				float num3 = Mathf.Max(0.05f, now - history.LastSeenTime);
				threatClosingIn = Mathf.Clamp01((Mathf.Max(0f, (history.LastThreatDistance - player.NearestThreatDistance) / num3) - 1f) / 5f);
			}
			return new SceneSignals(threatAppeared, threatClosingIn, fallStarted, passedOutStarted);
		}

		private static float SmoothScore(TargetHistory history, float sceneScore, SceneSignals signals, float now)
		{
			if (!history.HasSmoothedScore)
			{
				history.HasSmoothedScore = true;
				history.SmoothedScore = sceneScore;
				history.LastScoreTime = now;
				return sceneScore;
			}
			float num = Mathf.Max(0f, now - history.LastScoreTime);
			float num2 = ((sceneScore >= history.SmoothedScore) ? 0.45f : 1.1f);
			float num3 = 1f - Mathf.Exp((0f - num) / Mathf.Max(0.001f, num2));
			if (signals.PassedOutStarted || signals.FallStarted)
			{
				num3 = Mathf.Max(num3, 0.85f);
			}
			else if (signals.ThreatAppeared)
			{
				num3 = Mathf.Max(num3, 0.65f);
			}
			history.SmoothedScore = Mathf.Lerp(history.SmoothedScore, sceneScore, Mathf.Clamp01(num3));
			history.LastScoreTime = now;
			return history.SmoothedScore;
		}

		private static bool ShouldSwitch(TargetCandidate? current, TargetCandidate best, float now)
		{
			if (!current.HasValue)
			{
				return true;
			}
			if (current.Value.Player.StableId == best.Player.StableId)
			{
				return false;
			}
			Plugin instance = Plugin.Instance;
			if (now - lastSwitchTime < Mathf.Max(0f, instance.TargetSwitchMinInterval.Value))
			{
				return false;
			}
			float num = Mathf.Max(Mathf.Max(0f, instance.TargetSwitchScoreLead.Value), current.Value.Score * 0.15f);
			return best.Score >= current.Value.Score + num;
		}

		private static void SwitchTo(TargetCandidate candidate, TargetCandidate? previous, List<TargetCandidate> candidates, float now)
		{
			int num = currentTargetId;
			try
			{
				if (!SpectatorSession.TrySelect(candidate.Player.Character))
				{
					return;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Automatic spectator target switch failed: " + ex.Message));
				return;
			}
			if (!((Object)(object)MainCameraMovement.specCharacter != (Object)(object)candidate.Player.Character))
			{
				if (num != 0 && num != candidate.Player.StableId && Histories.TryGetValue(num, out TargetHistory value))
				{
					value.LastWatchedTime = now;
				}
				currentTargetId = candidate.Player.StableId;
				watchStartedTime = now;
				lastSwitchTime = now;
				EventFramer.Reset();
				LogSwitch(candidate, previous, candidates);
			}
		}

		private static void SyncCurrentWatch(int id, float now)
		{
			if (currentTargetId != id)
			{
				if (currentTargetId != 0 && Histories.TryGetValue(currentTargetId, out TargetHistory value))
				{
					value.LastWatchedTime = now;
				}
				currentTargetId = id;
				watchStartedTime = now;
			}
		}

		private static void UpdateHistories(IReadOnlyList<PlayerSnapshot> players, float now)
		{
			for (int i = 0; i < players.Count; i++)
			{
				PlayerSnapshot playerSnapshot = players[i];
				TargetHistory history = GetHistory(playerSnapshot.StableId);
				history.LastSeenTime = now;
				history.LastThreatCount = playerSnapshot.Threats.Count;
				history.LastThreatDistance = playerSnapshot.NearestThreatDistance;
				history.WasFalling = playerSnapshot.IsFalling;
				history.WasPassedOut = playerSnapshot.IsPassedOut;
			}
		}

		private static void PruneHistories(float now)
		{
			List<int> list = null;
			foreach (KeyValuePair<int, TargetHistory> history in Histories)
			{
				float num = Mathf.Max(history.Value.LastSeenTime, history.Value.LastWatchedTime);
				if (!(now - num <= 30f))
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(history.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					Histories.Remove(list[i]);
				}
			}
		}

		private static TargetHistory GetHistory(int id)
		{
			if (!Histories.TryGetValue(id, out TargetHistory value))
			{
				value = new TargetHistory();
				Histories[id] = value;
			}
			return value;
		}

		private static float GetCurrentTargetBonus(float now)
		{
			Plugin instance = Plugin.Instance;
			float num = Mathf.Clamp01((Mathf.Max(0f, now - watchStartedTime) - Mathf.Max(1f, instance.TargetMaxWatchSeconds.Value)) / 8f);
			return Mathf.Max(0f, instance.TargetStickinessBonus.Value) * (1f - num);
		}

		private static float GetRecentlyWatchedPenalty(TargetHistory history, float now)
		{
			if (history.LastWatchedTime <= 0f)
			{
				return 0f;
			}
			return 1f - Mathf.Clamp01((now - history.LastWatchedTime) / 12f);
		}

		private static void SetActive(bool active, bool showMessage, string reason)
		{
			//IL_0042: 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)
			if (Active != active)
			{
				Active = active;
				ResetSelectionState();
				if (active)
				{
					lastSwitchTime = -9999f;
				}
				if (showMessage)
				{
					Plugin.Instance.ShowStatus(active ? "Auto player selection: ON" : "Auto player selection: OFF", active ? Color.green : Color.yellow);
				}
				Plugin.Log.LogInfo((object)("Automatic player selection " + (active ? "enabled" : "disabled") + " (" + reason + ")."));
			}
		}

		private static void ResetSelectionState()
		{
			currentTargetId = 0;
			watchStartedTime = 0f;
			lastTelemetryVersion = int.MinValue;
		}

		private static void LogSwitch(TargetCandidate selected, TargetCandidate? previous, List<TargetCandidate> candidates)
		{
			if (Plugin.Instance.TargetDebugScores.Value)
			{
				candidates.Sort((TargetCandidate left, TargetCandidate right) => right.Score.CompareTo(left.Score));
				int num = Math.Min(3, candidates.Count);
				List<string> list = new List<string>(num);
				for (int num2 = 0; num2 < num; num2++)
				{
					list.Add(FormatCandidate(candidates[num2]));
				}
				Plugin.Log.LogInfo((object)("Auto target switch: selected=" + FormatCandidate(selected) + "; previous=" + (previous.HasValue ? FormatCandidate(previous.Value) : "none") + "; top=" + string.Join(" | ", list)));
			}
		}

		private static string FormatCandidate(TargetCandidate candidate)
		{
			SceneScore breakdown = candidate.Breakdown;
			return $"{candidate.Player.Name}#{candidate.Player.StableId} " + $"final={breakdown.Final:0.0} scene={breakdown.SmoothedScene:0.0}/{breakdown.Scene:0.0} " + $"threat={breakdown.Threat:0.0} peril={breakdown.Peril:0.0} social={breakdown.Social:0.0} " + $"activity={breakdown.Activity:0.0} frame={breakdown.FramePotential:0.0} " + $"sticky={breakdown.CurrentBonus:0.0} recent=-{breakdown.RecentlyWatchedPenalty:0.0}";
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal static class CameraDirector
	{
		private const float SoftArrivalAngle = 0.75f;

		private const float HoldAngle = 0.02f;

		private const float SoftArrivalMinimumScale = 0.08f;

		private const float TargetResetAngle = 55f;

		private static MainCameraMovement? rotationCamera;

		private static bool hasRotationTarget;

		private static Quaternion lastRotationTarget = Quaternion.identity;

		private static bool hasRotationAxis;

		private static Vector3 lastRotationAxis = Vector3.forward;

		private static float angularSpeedDegrees;

		private static Character? outputRotationTarget;

		private static bool hasOutputRotation;

		private static Quaternion outputRotation = Quaternion.identity;

		private static Character? positionTarget;

		private static bool hasSmoothedPosition;

		private static Vector3 smoothedPosition;

		private static MainCameraMovement? outputCamera;

		private static Camera? renderCamera;

		private static int lastAppliedFrame = -1;

		internal static void Apply(MainCameraMovement cameraMovement)
		{
			//IL_00a5: 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_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_00c4: 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_00c6: 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_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: 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)
			Plugin instance = Plugin.Instance;
			Character localCharacter = Character.localCharacter;
			if (instance.LookMode == SpectatorLookMode.Off || !Plugin.SpectatorControlsAllowed() || (Object)(object)localCharacter == (Object)null || !localCharacter.data.dead || (Object)(object)MainCameraMovement.specCharacter == (Object)null || (Object)(object)MainCameraMovement.specCharacter == (Object)(object)localCharacter || (Object)(object)MainCameraMovement.specCharacter.data == (Object)null)
			{
				ResetRotation();
				return;
			}
			Character specCharacter = MainCameraMovement.specCharacter;
			if ((Object)(object)outputCamera != (Object)(object)cameraMovement || lastAppliedFrame != Time.frameCount - 1)
			{
				ResetRotation();
				outputCamera = cameraMovement;
				renderCamera = ((Component)cameraMovement).GetComponent<Camera>();
			}
			lastAppliedFrame = Time.frameCount;
			Quaternion val = Quaternion.LookRotation(CameraMath.WithPitchOffset(SpectatorTelemetry.GetLiveLookDirection(specCharacter), instance.PlayerPitchDegrees.Value), Vector3.up);
			Quaternion desired = val;
			int num;
			if (instance.LookMode == SpectatorLookMode.EventFraming)
			{
				num = (instance.EventLookEnabled.Value ? 1 : 0);
				if (num != 0)
				{
					ApplyCameraPosition(cameraMovement, specCharacter, val);
					float num2 = instance.EventLookMaxFrameAngle.Value;
					if ((Object)(object)renderCamera != (Object)null)
					{
						float num3 = Camera.VerticalToHorizontalFieldOfView(renderCamera.fieldOfView, renderCamera.aspect);
						num2 = Mathf.Min(num2, Mathf.Min(renderCamera.fieldOfView, num3) - 4f);
					}
					if (num2 > 0f && EventFramer.TryGetDesiredDirection(((Component)cameraMovement).transform.position, specCharacter, num2, instance.EventPitchDegrees.Value, out var direction))
					{
						desired = Quaternion.LookRotation(direction, Vector3.up);
					}
				}
			}
			else
			{
				num = 0;
			}
			Quaternion rotation = ApplyRotation(springSeconds: instance.SmoothRotationEnabled ? Mathf.Max(0.01f, instance.SmoothRotationSpringSeconds.Value) : Mathf.Max(0f, instance.RotationSpringSeconds.Value), maxSpeed: instance.SmoothRotationEnabled ? Mathf.Max(0f, instance.SmoothRotationMaxSpeedDegrees.Value) : Mathf.Max(0f, instance.RotationMaxSpeedDegrees.Value), cameraMovement: cameraMovement, target: specCharacter, desired: desired, smoothing: Mathf.Max(0f, instance.RotationSmoothing.Value), forceSpring: instance.SmoothRotationEnabled);
			((Component)cameraMovement).transform.rotation = rotation;
			if (num == 0)
			{
				ApplyCameraPosition(cameraMovement, specCharacter, rotation);
			}
			SyncSpectatorGhostDirection(rotation);
		}

		internal static void ResetRotation()
		{
			//IL_0011: 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_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			ResetRotationDriver();
			outputRotationTarget = null;
			hasOutputRotation = false;
			outputRotation = Quaternion.identity;
			positionTarget = null;
			hasSmoothedPosition = false;
			smoothedPosition = Vector3.zero;
			outputCamera = null;
			renderCamera = null;
			lastAppliedFrame = -1;
		}

		internal static void ResetRotationMotion()
		{
			ResetRotationDriver();
		}

		private static void ResetRotationDriver()
		{
			//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_001c: 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)
			rotationCamera = null;
			hasRotationTarget = false;
			lastRotationTarget = Quaternion.identity;
			hasRotationAxis = false;
			lastRotationAxis = Vector3.forward;
			angularSpeedDegrees = 0f;
		}

		private static void ApplyCameraPosition(MainCameraMovement cameraMovement, Character target, Quaternion rotation)
		{
			//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_0056: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: 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_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_0103: 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_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)localCharacter == (Object)null) && !((Object)(object)localCharacter.data == (Object)null))
			{
				float num = Mathf.Clamp(localCharacter.data.spectateZoom, cameraMovement.spectateZoomMin, cameraMovement.spectateZoomMax);
				Vector3 val;
				try
				{
					val = target.GetSpectatePosition();
				}
				catch (Exception)
				{
					val = SpectatorTelemetry.GetLivePosition(target, ((Component)cameraMovement).transform.position);
				}
				Vector3 val2 = val + rotation * new Vector3(0f, 0.5f, 0f - num) + Vector3.up * Mathf.Clamp(Plugin.Instance.CameraVerticalOffset.Value, -3f, 3f);
				float num2 = Mathf.Max(0f, Plugin.Instance.PositionSmoothing.Value);
				bool flag = (Object)(object)positionTarget != (Object)(object)target;
				bool flag2 = hasSmoothedPosition && Vector3.Distance(smoothedPosition, val2) > 20f;
				if (!hasSmoothedPosition || flag || flag2 || num2 <= 0f)
				{
					smoothedPosition = val2;
					hasSmoothedPosition = true;
				}
				else
				{
					float num3 = 1f - Mathf.Exp((0f - num2) * Time.unscaledDeltaTime);
					smoothedPosition = Vector3.Lerp(smoothedPosition, val2, Mathf.Clamp01(num3));
				}
				positionTarget = target;
				((Component)cameraMovement).transform.position = smoothedPosition;
			}
		}

		private static void SyncSpectatorGhostDirection(Quaternion rotation)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: 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_009c: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: 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)
			//IL_00b4: 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_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: 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_0113: 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_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: 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_0142: 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_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_00cf: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)localCharacter == (Object)null) && !((Object)(object)localCharacter.data == (Object)null) && localCharacter.data.dead && !((Object)(object)MainCameraMovement.specCharacter == (Object)(object)localCharacter))
			{
				Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles;
				float num = Mathf.Clamp(NormalizeAngle(eulerAngles.x), -85f, 85f);
				float num2 = localCharacter.data.lookValues.x + Mathf.DeltaAngle(localCharacter.data.lookValues.x, eulerAngles.y);
				localCharacter.data.lookValues = new Vector2(num2, 0f - num);
				Vector3 val = rotation * Vector3.forward;
				Vector3 val2 = Vector3.ProjectOnPlane(val, Vector3.up);
				if (((Vector3)(ref val2)).sqrMagnitude <= 0.0001f)
				{
					val2 = rotation * Vector3.forward;
					val2.y = 0f;
				}
				localCharacter.data.lookDirection = ((Vector3)(ref val)).normalized;
				localCharacter.data.lookDirection_Flat = ((Vector3)(ref val2)).normalized;
				CharacterData data = localCharacter.data;
				Vector3 val3 = Vector3.Cross(Vector3.up, val);
				data.lookDirection_Right = ((Vector3)(ref val3)).normalized;
				CharacterData data2 = localCharacter.data;
				val3 = Vector3.Cross(val, localCharacter.data.lookDirection_Right);
				data2.lookDirection_Up = ((Vector3)(ref val3)).normalized;
			}
		}

		private static float NormalizeAngle(float angle)
		{
			angle %= 360f;
			if (angle > 180f)
			{
				angle -= 360f;
			}
			else if (angle < -180f)
			{
				angle += 360f;
			}
			return angle;
		}

		private static Quaternion ApplyRotation(MainCameraMovement cameraMovement, Character target, Quaternion desired, float smoothing, float springSeconds, float maxSpeed, bool forceSpring)
		{
			//IL_001a: 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_0021: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			//IL_0061: 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_004a: 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_0068: 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)
			Quaternion current = ((hasOutputRotation && (Object)(object)outputRotationTarget == (Object)(object)target) ? outputRotation : ((Component)cameraMovement).transform.rotation);
			Quaternion result;
			if (smoothing <= 0f && !forceSpring)
			{
				ResetRotationDriver();
				result = desired;
			}
			else
			{
				result = ((forceSpring || springSeconds > 0.0001f) ? SpringRotation(cameraMovement, current, desired, springSeconds, maxSpeed) : SmoothRotation(current, desired, smoothing, maxSpeed));
			}
			outputRotationTarget = target;
			outputRotation = result;
			hasOutputRotation = true;
			return result;
		}

		private static Quaternion SpringRotation(MainCameraMovement cameraMovement, Quaternion current, Quaternion desired, float springSeconds, float maxSpeed)
		{
			//IL_0010: 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_000e: 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_005a: 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_005c: 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_0042: 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_008b: 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_0097: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			float unscaledDeltaTime = Time.unscaledDeltaTime;
			if (unscaledDeltaTime <= 0f)
			{
				return current;
			}
			float num = Quaternion.Angle(current, desired);
			if (num <= 0.02f)
			{
				ResetRotationFor(cameraMovement);
				return current;
			}
			if ((Object)(object)rotationCamera != (Object)(object)cameraMovement)
			{
				ResetRotationFor(cameraMovement);
			}
			if (hasRotationTarget && Quaternion.Angle(lastRotationTarget, desired) > 55f)
			{
				ResetRotationFor(cameraMovement);
			}
			Vector3 shortestRotationAxis = GetShortestRotationAxis(current, desired);
			if (hasRotationAxis && Vector3.Dot(lastRotationAxis, shortestRotationAxis) < 0f)
			{
				angularSpeedDegrees = 0f;
			}
			rotationCamera = cameraMovement;
			lastRotationTarget = desired;
			hasRotationTarget = true;
			lastRotationAxis = shortestRotationAxis;
			hasRotationAxis = true;
			float num2 = Mathf.Max(0.001f, springSeconds);
			float softArrivalScale = GetSoftArrivalScale(num);
			float num3 = num / num2 * softArrivalScale;
			float num4 = ((maxSpeed > 0f) ? maxSpeed : Mathf.Max(num3, 360f));
			num4 *= softArrivalScale;
			num3 = Mathf.Min(num3, num4);
			float num5 = Mathf.Max(1f, num4 / num2);
			float num6 = Mathf.Sqrt(2f * num5 * Mathf.Max(0f, num - 0.02f)) * softArrivalScale;
			if (num6 <= 0f)
			{
				angularSpeedDegrees = 0f;
				return current;
			}
			angularSpeedDegrees = Mathf.Min(angularSpeedDegrees, num6);
			num3 = Mathf.Min(num3, num6);
			angularSpeedDegrees = Mathf.MoveTowards(angularSpeedDegrees, num3, num5 * unscaledDeltaTime);
			float num7 = ClampRotationStep(num, angularSpeedDegrees * unscaledDeltaTime);
			if (!(num7 > 0f))
			{
				return current;
			}
			return Quaternion.RotateTowards(current, desired, num7);
		}

		private static Quaternion SmoothRotation(Quaternion current, Quaternion desired, float smoothing, float maxSpeed)
		{
			//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_0021: 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_0074: 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)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			ResetRotationDriver();
			float num = Quaternion.Angle(current, desired);
			if (num <= 0.02f || Time.unscaledDeltaTime <= 0f)
			{
				return current;
			}
			float softArrivalScale = GetSoftArrivalScale(num);
			float num2 = 1f - Mathf.Exp((0f - smoothing) * Time.unscaledDeltaTime);
			float num3 = num * Mathf.Clamp01(num2) * softArrivalScale;
			if (maxSpeed > 0f)
			{
				num3 = Mathf.Min(num3, maxSpeed * Time.unscaledDeltaTime * softArrivalScale);
			}
			num3 = ClampRotationStep(num, num3);
			if (!(num3 > 0f))
			{
				return current;
			}
			return Quaternion.RotateTowards(current, desired, num3);
		}

		private static float GetSoftArrivalScale(float angle)
		{
			if (angle >= 0.75f)
			{
				return 1f;
			}
			float num = Mathf.Clamp01(angle / 0.75f);
			return Mathf.Lerp(0.08f, 1f, num * num * num);
		}

		private static float ClampRotationStep(float angle, float step)
		{
			if (angle <= 0.02f || step <= 0f)
			{
				return 0f;
			}
			return Mathf.Min(step, Mathf.Max(0f, angle - 0.02f));
		}

		private static Vector3 GetShortestRotationAxis(Quaternion current, Quaternion desired)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//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_000c: 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_001c: 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_0038: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			Quaternion val = desired * Quaternion.Inverse(current);
			if (val.w < 0f)
			{
				val.x = 0f - val.x;
				val.y = 0f - val.y;
				val.z = 0f - val.z;
				val.w = 0f - val.w;
			}
			float num = default(float);
			Vector3 val2 = default(Vector3);
			((Quaternion)(ref val)).ToAngleAxis(ref num, ref val2);
			if (num > 180f)
			{
				val2 = -val2;
			}
			if (!(((Vector3)(ref val2)).sqrMagnitude > 0.0001f))
			{
				return lastRotationAxis;
			}
			return ((Vector3)(ref val2)).normalized;
		}

		private static void ResetRotationFor(MainCameraMovement cameraMovement)
		{
			ResetRotationDriver();
			rotationCamera = cameraMovement;
		}
	}
	internal static class CameraMath
	{
		internal static Vector3 WithPitchOffset(Vector3 direction, float degrees)
		{
			//IL_001b: 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_002d: 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_000e: Unknown result type (might be due to invalid IL or missing references)
			if (((Vector3)(ref direction)).sqrMagnitude < 0.0001f)
			{
				return Vector3.forward;
			}
			((Vector3)(ref direction)).Normalize();
			float num = Mathf.Atan2(direction.x, direction.z);
			float num2 = Mathf.Clamp((0f - Mathf.Asin(Mathf.Clamp(direction.y, -1f, 1f))) * 57.29578f + degrees, -85f, 85f) * (MathF.PI / 180f);
			float num3 = Mathf.Cos(num2);
			return new Vector3(Mathf.Sin(num) * num3, 0f - Mathf.Sin(num2), Mathf.Cos(num) * num3);
		}
	}
	internal static class EventFramer
	{
		private readonly struct FrameSubject
		{
			internal long Id { get; }

			internal Component Source { get; }

			internal float Score { get; }

			internal bool IsAnchor { get; }

			internal Vector3 Position { get; }

			internal FrameSubject(long id, Component source, float score, bool isAnchor)
			{
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: 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_0045: 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_004a: Unknown result type (might be due to invalid IL or missing references)
				Id = id;
				Source = source;
				Score = score;
				IsAnchor = isAnchor;
				Character val = (Character)(object)((source is Character) ? source : null);
				Position = ((val != null) ? SpectatorTelemetry.GetLiveHeadPosition(val, SpectatorTelemetry.GetLivePosition(val)) : source.transform.position);
			}

			internal bool TryGetPosition(out Vector3 position)
			{
				//IL_0001: 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_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_005e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_006a: 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)
				position = Vector3.zero;
				if ((Object)(object)Source == (Object)null || !Source.gameObject.activeInHierarchy)
				{
					return false;
				}
				Component source = Source;
				Character val = (Character)(object)((source is Character) ? source : null);
				if (val != null)
				{
					if ((Object)(object)val.data == (Object)null || val.data.dead)
					{
						return false;
					}
					position = SpectatorTelemetry.GetLiveHeadPosition(val, SpectatorTelemetry.GetLivePosition(val));
				}
				else
				{
					Component source2 = Source;
					Item val2 = (Item)(object)((source2 is Item) ? source2 : null);
					if (val2 != null && ((Object)(object)MainCameraMovement.specCharacter == (Object)null || (Object)(object)MainCameraMovement.specCharacter.data.currentItem != (Object)(object)val2))
					{
						return false;
					}
					position = Source.transform.position;
				}
				return true;
			}
		}

		private static readonly List<FrameSubject> Subjects = new List<FrameSubject>();

		private static readonly List<FrameSubject> Held = new List<FrameSubject>();

		private static readonly List<FrameSubject> Best = new List<FrameSubject>();

		private static readonly List<FrameSubject> Candidate = new List<FrameSubject>();

		private static readonly List<FramePoint> Points = new List<FramePoint>();

		private static int targetId;

		private static int telemetryVersion = -1;

		private static float holdStartedTime;

		internal static bool TryGetDesiredDirection(Vector3 origin, Character character, float frameAngle, float pitch, out Vector3 direction)
		{
			//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_005c: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			direction = Vector3.forward;
			if (!SpectatorTelemetry.IsSelectableNow(character) || !SpectatorTelemetry.TryGet(character, out PlayerSnapshot snapshot) || snapshot == null)
			{
				Reset();
				return false;
			}
			if (targetId != snapshot.StableId)
			{
				Reset();
				targetId = snapshot.StableId;
			}
			if (telemetryVersion != SpectatorTelemetry.Version)
			{
				telemetryVersion = SpectatorTelemetry.Version;
				BuildSubjects(snapshot, origin);
				SelectGroup(origin, frameAngle, pitch);
			}
			if (Held.Count > 1 && FillPoints(Held, Points))
			{
				return FrameGeometry.TryDirection(Points, origin, frameAngle, pitch, out direction);
			}
			return false;
		}

		internal static void Reset()
		{
			targetId = 0;
			telemetryVersion = -1;
			holdStartedTime = 0f;
			Subjects.Clear();
			Held.Clear();
			Best.Clear();
			Candidate.Clear();
			Points.Clear();
		}

		private static void BuildSubjects(PlayerSnapshot target, Vector3 origin)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: 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_015d: 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_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			Subjects.Clear();
			Plugin instance = Plugin.Instance;
			float value = instance.EventLookFocusRange.Value;
			Vector3 livePosition = SpectatorTelemetry.GetLivePosition(target.Character);
			FrameSubject frameSubject = new FrameSubject((long)target.StableId << 2, (Component)(object)target.Character, 28f, isAnchor: true);
			if (!Visible(frameSubject, origin, instance))
			{
				return;
			}
			Subjects.Add(frameSubject);
			for (int i = 0; i < target.Threats.Count; i++)
			{
				ThreatSnapshot threat = target.Threats[i];
				if (!((Object)(object)threat.Character == (Object)null) && !((Object)(object)threat.Character.data == (Object)null) && !threat.Character.data.dead)
				{
					float num = Vector3.Distance(livePosition, SpectatorTelemetry.GetLivePosition(threat.Character));
					FrameSubject frameSubject2 = new FrameSubject(((long)threat.StableId << 2) | 1, (Component)(object)threat.Character, SceneEvaluator.GetEventThreatScore(threat, value), isAnchor: false);
					if (num <= value && Visible(frameSubject2, origin, instance))
					{
						Subjects.Add(frameSubject2);
					}
				}
			}
			IReadOnlyList<PlayerSnapshot> players = SpectatorTelemetry.Players;
			for (int j = 0; j < players.Count; j++)
			{
				PlayerSnapshot playerSnapshot = players[j];
				if (playerSnapshot.StableId != target.StableId && SpectatorTelemetry.IsSelectableNow(playerSnapshot.Character))
				{
					float num2 = Vector3.Distance(livePosition, SpectatorTelemetry.GetLivePosition(playerSnapshot.Character));
					FrameSubject frameSubject3 = new FrameSubject(((long)playerSnapshot.StableId << 2) | 2, (Component)(object)playerSnapshot.Character, SceneEvaluator.GetEventPlayerScore(playerSnapshot, num2, value), isAnchor: false);
					if (num2 <= value && Visible(frameSubject3, origin, instance))
					{
						Subjects.Add(frameSubject3);
					}
				}
			}
			Item currentItem = target.Character.data.currentItem;
			if ((Object)(object)currentItem != (Object)null && Vector3.Distance(livePosition, currentItem.transform.position) <= value)
			{
				FrameSubject frameSubject4 = new FrameSubject(((long)((Object)currentItem).GetInstanceID() << 2) | 3, (Component)(object)currentItem, target.IsUsingItem ? 42f : 18f, isAnchor: false);
				if (Visible(frameSubject4, origin, instance))
				{
					Subjects.Add(frameSubject4);
				}
			}
			Subjects.Sort(CompareSubjects);
		}

		private static int CompareSubjects(FrameSubject left, FrameSubject right)
		{
			if (left.IsAnchor != right.IsAnchor)
			{
				if (!left.IsAnchor)
				{
					return 1;
				}
				return -1;
			}
			int num = right.Score.CompareTo(left.Score);
			if (num == 0)
			{
				return left.Id.CompareTo(right.Id);
			}
			return num;
		}

		private static void SelectGroup(Vector3 origin, float angle, float pitch)
		{
			//IL_001e: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			Vector3 direction;
			bool flag = RefreshHeldSubjects() && FillPoints(Held, Points, live: false) && FrameGeometry.TryDirection(Points, origin, angle, pitch, out direction);
			float currentScore = (flag ? Score(Held) : 0f);
			Best.Clear();
			float num = float.NegativeInfinity;
			for (int i = 1; i < Subjects.Count; i++)
			{
				Candidate.Clear();
				Candidate.Add(Subjects[0]);
				if (!TryAdd(Subjects[i], origin, angle, pitch))
				{
					continue;
				}
				for (int j = 1; j < Subjects.Count; j++)
				{
					if (j != i)
					{
						TryAdd(Subjects[j], origin, angle, pitch);
					}
				}
				float num2 = Score(Candidate);
				if (num2 > num)
				{
					num = num2;
					Best.Clear();
					Best.AddRange(Candidate);
				}
			}
			Plugin instance = Plugin.Instance;
			float unscaledTime = Time.unscaledTime;
			if (!FrameGeometry.KeepCurrent(flag, currentScore, num, unscaledTime - holdStartedTime, instance.EventLookMinHoldSeconds.Value, instance.EventLookSwitchScoreLead.Value))
			{
				Held.Clear();
				Held.AddRange(Best);
				holdStartedTime = unscaledTime;
			}
		}

		private static bool RefreshHeldSubjects()
		{
			if (Held.Count < 2)
			{
				return false;
			}
			for (int i = 0; i < Held.Count; i++)
			{
				int num = -1;
				for (int j = 0; j < Subjects.Count; j++)
				{
					if (Subjects[j].Id == Held[i].Id)
					{
						num = j;
						break;
					}
				}
				if (num < 0)
				{
					return false;
				}
				Held[i] = Subjects[num];
			}
			return true;
		}

		private static bool TryAdd(FrameSubject subject, Vector3 origin, float angle, float pitch)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			Candidate.Add(subject);
			if (FillPoints(Candidate, Points, live: false) && FrameGeometry.TryDirection(Points, origin, angle, pitch, out var _))
			{
				return true;
			}
			Candidate.RemoveAt(Candidate.Count - 1);
			return false;
		}

		private static bool FillPoints(List<FrameSubject> subjects, List<FramePoint> points, bool live = true)
		{
			//IL_0014: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			points.Clear();
			for (int i = 0; i < subjects.Count; i++)
			{
				FrameSubject frameSubject = subjects[i];
				Vector3 position = frameSubject.Position;
				if (live && !frameSubject.TryGetPosition(out position))
				{
					return false;
				}
				points.Add(new FramePoint(position, frameSubject.Score));
			}
			return true;
		}

		private static float Score(List<FrameSubject> members)
		{
			float num = 0f;
			float num2 = 0f;
			for (int i = 0; i < members.Count; i++)
			{
				if (!members[i].IsAnchor)
				{
					num = Mathf.Max(num, members[i].Score);
					num2 += members[i].Score;
				}
			}
			return num + Mathf.Min(35f, (num2 - num) * 0.28f) + Mathf.Min(24f, (float)(members.Count - 1) * 7f) + 9f;
		}

		private static bool Visible(FrameSubject subject, Vector3 origin, Plugin plugin)
		{
			//IL_000d: 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)
			if (plugin.LineOfSightChecks.Value)
			{
				return SpectatorTelemetry.HasLineOfSight(origin, subject.Position, subject.Source);
			}
			return true;
		}
	}
	internal readonly struct FramePoint
	{
		internal Vector3 Position { get; }

		internal float Weight { get; }

		internal FramePoint(Vector3 position, float weight)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Position = position;
			Weight = weight;
		}
	}
	internal static class FrameGeometry
	{
		internal static bool TryDirection(IReadOnlyList<FramePoint> points, Vector3 origin, float frameAngle, float pitchOffset, out Vector3 direction)
		{
			//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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_003a: 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_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_008f: 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_00b9: 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_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_00d8: Unknown result type (might be due to invalid IL or missing references)
			direction = Vector3.zero;
			for (int i = 0; i < points.Count; i++)
			{
				Vector3 val = points[i].Position - origin;
				if (((Vector3)(ref val)).sqrMagnitude < 0.0001f)
				{
					return false;
				}
				direction += ((Vector3)(ref val)).normalized * Mathf.Max(1f, points[i].Weight);
			}
			if (((Vector3)(ref direction)).sqrMagnitude < 0.0001f)
			{
				return false;
			}
			direction = CameraMath.WithPitchOffset(direction, pitchOffset);
			float num = Mathf.Cos(frameAngle * 0.5f * (MathF.PI / 180f));
			for (int j = 0; j < points.Count; j++)
			{
				Vector3 val2 = direction;
				Vector3 val3 = points[j].Position - origin;
				if (Vector3.Dot(val2, ((Vector3)(ref val3)).normalized) < num - 1E-05f)
				{
					return false;
				}
			}
			return true;
		}

		internal static bool KeepCurrent(bool currentValid, float currentScore, float bestScore, float elapsed, float holdSeconds, float requiredLead)
		{
			if (currentValid)
			{
				if (!(elapsed < holdSeconds))
				{
					return bestScore <= currentScore + requiredLead;
				}
				return true;
			}
			return false;
		}
	}
	internal sealed class LiveConfigReloader
	{
		private readonly ConfigFile config;

		private (long Written, long Length)? observed;

		private (long Written, long Length)? applied;

		private float nextPoll;

		private float changedAt;

		private string? lastError;

		internal LiveConfigReloader(ConfigFile config)
		{
			this.config = config;
			observed = (applied = ReadStamp());
		}

		internal bool Tick(float now, out string? error)
		{
			error = null;
			if (now < nextPoll)
			{
				return false;
			}
			nextPoll = now + 0.25f;
			try
			{
				(long, long)? tuple = ReadStamp();
				(long, long)? tuple2 = tuple;
				(long, long)? tuple3 = observed;
				bool hasValue = tuple2.HasValue;
				if (hasValue != tuple3.HasValue)
				{
					goto IL_0080;
				}
				if (hasValue)
				{
					(long, long) valueOrDefault = tuple2.GetValueOrDefault();
					(long, long) valueOrDefault2 = tuple3.GetValueOrDefault();
					if (valueOrDefault.Item1 != valueOrDefault2.Item1 || valueOrDefault.Item2 != valueOrDefault2.Item2)
					{
						goto IL_0080;
					}
				}
				goto IL_0095;
				IL_0095:
				if (tuple.HasValue)
				{
					tuple3 = tuple;
					tuple2 = applied;
					hasValue = tuple3.HasValue;
					if (hasValue != tuple2.HasValue)
					{
						goto IL_00f9;
					}
					if (hasValue)
					{
						(long, long) valueOrDefault2 = tuple3.GetValueOrDefault();
						(long, long) valueOrDefault = tuple2.GetValueOrDefault();
						if (valueOrDefault2.Item1 != valueOrDefault.Item1 || valueOrDefault2.Item2 != valueOrDefault.Item2)
						{
							goto IL_00f9;
						}
					}
				}
				goto IL_0108;
				IL_00f9:
				if (!(now - changedAt < 0.5f))
				{
					try
					{
						return ReloadNow();
					}
					catch (FormatException)
					{
						applied = tuple;
						throw;
					}
				}
				goto IL_0108;
				IL_0108:
				return false;
				IL_0080:
				observed = tuple;
				changedAt = now;
				lastError = null;
				goto IL_0095;
			}
			catch (Exception ex2) when (ex2 is IOException || ex2 is UnauthorizedAccessException || ex2 is FormatException)
			{
				if (lastError != ex2.Message)
				{
					error = ex2.Message;
				}
				lastError = ex2.Message;
				return false;
			}
		}

		internal bool ReloadNow()
		{
			(long, long)? tuple = ReadStamp();
			string text = File.ReadAllText(config.ConfigFilePath);
			(long, long)? tuple2 = ReadStamp();
			if (tuple.HasValue)
			{
				(long, long)? tuple3 = tuple;
				(long, long)? tuple4 = tuple2;
				bool hasValue = tuple3.HasValue;
				if (hasValue == tuple4.HasValue)
				{
					if (hasValue)
					{
						(long, long) valueOrDefault = tuple3.GetValueOrDefault();
						(long, long) valueOrDefault2 = tuple4.GetValueOrDefault();
						if (valueOrDefault.Item1 != valueOrDefault2.Item1 || valueOrDefault.Item2 != valueOrDefault2.Item2)
						{
							goto IL_0085;
						}
					}
					bool result = ApplyText(config, text);
					observed = (applied = tuple2);
					lastError = null;
					return result;
				}
			}
			goto IL_0085;
			IL_0085:
			throw new IOException("Config file changed while reading; waiting for the next stable save.");
		}

		private (long Written, long Length)? ReadStamp()
		{
			FileInfo fileInfo = new FileInfo(config.ConfigFilePath);
			if (!fileInfo.Exists)
			{
				return null;
			}
			return (fileInfo.LastWriteTimeUtc.Ticks, fileInfo.Length);
		}

		internal static bool ApplyText(ConfigFile config, string text)
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			Dictionary<ConfigEntryBase, object> dictionary = new Dictionary<ConfigEntryBase, object>();
			string text2 = string.Empty;
			using StringReader stringReader = new StringReader(text);
			string text3;
			while ((text3 = stringReader.ReadLine()) != null)
			{
				text3 = text3.Trim();
				if (text3.Length == 0 || text3.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text3.StartsWith("[", StringComparison.Ordinal) && text3.EndsWith("]", StringComparison.Ordinal))
				{
					text2 = text3.Substring(1, text3.Length - 2);
					continue;
				}
				int num = text3.IndexOf('=');
				if (num <= 0)
				{
					throw new FormatException("Incomplete config entry; previous settings have been kept.");
				}
				string text4 = text3.Substring(0, num).Trim();
				try
				{
					ConfigDefinition key = new ConfigDefinition(text2, text4);
					if (((IDictionary<ConfigDefinition, ConfigEntryBase>)config).TryGetValue(key, out ConfigEntryBase value) && value != null)
					{
						object obj = TomlTypeConverter.ConvertToValue(text3.Substring(num + 1).Trim(), value.SettingType);
						if (obj is float f && (float.IsNaN(f) || float.IsInfinity(f)))
						{
							throw new FormatException("A finite number is required.");
						}
						dictionary[value] = obj;
					}
				}
				catch (Exception ex)
				{
					throw new FormatException("Invalid setting [" + text2 + "] " + text4 + ": " + ex.Message, ex);
				}
			}
			if (dictionary.Count == 0)
			{
				throw new FormatException("No known settings found; previous settings have been kept.");
			}
			bool saveOnConfigSet = config.SaveOnConfigSet;
			bool result = false;
			try
			{
				config.SaveOnConfigSet = false;
				foreach (KeyValuePair<ConfigEntryBase, object> item in dictionary)
				{
					AcceptableValueBase acceptableValues = item.Key.Description.AcceptableValues;
					object obj2 = ((acceptableValues != null) ? acceptableValues.Clamp(item.Value) : null) ?? item.Value;
					if (!object.Equals(item.Key.BoxedValue, obj2))
					{
						item.Key.BoxedValue = obj2;
						result = true;
					}
				}
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
			return result;
		}
	}
	[BepInPlugin("com.tactikot.peak.autocameraspectator", "Auto Camera Spectator", "1.0.5")]
	[BepInProcess("PEAK.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.tactikot.peak.autocameraspectator";

		public const string PluginName = "Auto Camera Spectator";

		public const string PluginVersion = "1.0.5";

		private const float PitchStepDegrees = 2.5f;

		private const float HeightStepMeters = 0.25f;

		private Harmony? harmony;

		private GUIStyle? overlayStyle;

		private string overlayMessage = string.Empty;

		private Color overlayColor = Color.white;

		private float overlayUntil;

		private LiveConfigReloader? configReloader;

		private int settingsChanged;

		private bool wasSpectating;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger;

		internal ConfigEntry<bool> EnabledByDefault { get; private set; }

		internal ConfigEntry<float> PlayerPitchDegrees { get; private set; }

		internal ConfigEntry<float> EventPitchDegrees { get; private set; }

		internal ConfigEntry<float> CameraVerticalOffset { get; private set; }

		internal ConfigEntry<float> RotationSmoothing { get; private set; }

		internal ConfigEntry<float> RotationSpringSeconds { get; private set; }

		internal ConfigEntry<float> RotationMaxSpeedDegrees { get; private set; }

		internal ConfigEntry<float> SmoothRotationSpringSeconds { get; private set; }

		internal ConfigEntry<float> SmoothRotationMaxSpeedDegrees { get; private set; }

		internal ConfigEntry<float> PositionSmoothing { get; private set; }

		internal ConfigEntry<float> TelemetryRefreshInterval { get; private set; }

		internal ConfigEntry<float> VoiceLoudnessThreshold { get; private set; }

		internal ConfigEntry<float> ThreatAwarenessRange { get; private set; }

		internal ConfigEntry<bool> LineOfSightChecks { get; private set; }

		internal ConfigEntry<bool> TargetSelectionEnabled { get; private set; }

		internal ConfigEntry<float> TargetSwitchMinInterval { get; private set; }

		internal ConfigEntry<float> TargetSwitchScoreLead { get; private set; }

		internal ConfigEntry<float> TargetStickinessBonus { get; private set; }

		internal ConfigEntry<float> TargetMaxWatchSeconds { get; private set; }

		internal ConfigEntry<float> NearbyPlayerRange { get; private set; }

		internal ConfigEntry<bool> TargetDebugScores { get; private set; }

		internal ConfigEntry<bool> EventLookEnabled { get; private set; }

		internal ConfigEntry<float> EventLookFocusRange { get; private set; }

		internal ConfigEntry<float> EventLookMaxFrameAngle { get; private set; }

		internal ConfigEntry<float> EventLookSwitchScoreLead { get; private set; }

		internal ConfigEntry<float> EventLookMinHoldSeconds { get; private set; }

		internal ConfigEntry<KeyboardShortcut> PlayerLookToggleKey { get; private set; }

		internal ConfigEntry<KeyboardShortcut> TargetSelectionToggleKey { get; private set; }

		internal ConfigEntry<KeyboardShortcut> EventLookToggleKey { get; private set; }

		internal ConfigEntry<KeyboardShortcut> SmoothRotationToggleKey { get; private set; }

		internal ConfigEntry<KeyboardShortcut> ConfigReloadKey { get; private set; }

		internal SpectatorLookMode LookMode { get; private set; }

		internal bool SmoothRotationEnabled { get; private set; }

		private void Awake()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			Instance = this;
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			BindConfig();
			SanitizeNumbers();
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			((BaseUnityPlugin)this).Config.Save();
			configReloader = new LiveConfigReloader(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
			LookMode = (EnabledByDefault.Value ? SpectatorLookMode.PlayerLook : SpectatorLookMode.Off);
			SmoothRotationEnabled = false;
			harmony = new Harmony("com.tactikot.peak.autocameraspectator");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Auto Camera Spectator v1.0.5 loaded. Camera mode: " + GetModeLabel(LookMode) + "."));
		}

		private void OnDestroy()
		{
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
			configReloader = null;
			CameraDirector.ResetRotation();
			SpectatorTelemetry.Clear();
			AutoTargetSelector.Reset();
			EventFramer.Reset();
		}

		private void BindConfig()
		{
			//IL_042f: Unknown result type (might be due to invalid IL or missing references)
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0487: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04df: Unknown result type (might be due to invalid IL or missing references)
			EnabledByDefault = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnabledByDefault", true, "Start with automatic player-look camera enabled.");
			PlayerPitchDegrees = BindFloat("General", "PlayerPitchDegrees", 15f, -45f, 45f, "Positive values tilt the player-look camera downward.");
			EventPitchDegrees = BindFloat("General", "EventPitchDegrees", 0f, -45f, 45f, "Positive values tilt event framing downward.");
			CameraVerticalOffset = BindFloat("General", "CameraVerticalOffset", 0.5f, -3f, 3f, "World-space vertical camera offset in PEAK units.");
			RotationSmoothing = BindFloat("General", "RotationSmoothing", 6f, 0f, 120f, "Camera rotation follow speed. Lower values are smoother; zero snaps instantly.");
			RotationSpringSeconds = BindFloat("General", "RotationSpringSeconds", 0f, 0f, 30f, "Optional no-overshoot damping time. Zero disables it.");
			RotationMaxSpeedDegrees = BindFloat("General", "RotationMaxSpeedDegrees", 90f, 0f, 3600f, "Maximum automatic rotation speed. Lower values are smoother; zero disables the cap.");
			SmoothRotationSpringSeconds = BindFloat("General", "SmoothRotationSpringSeconds", 0.22f, 0.01f, 30f, "Acceleration/deceleration time used while smooth rotation mode is enabled with S.");
			SmoothRotationMaxSpeedDegrees = BindFloat("General", "SmoothRotationMaxSpeedDegrees", 140f, 0f, 3600f, "Maximum speed used by smooth rotation mode. This is separate so normal mode stays unchanged.");
			PositionSmoothing = BindFloat("General", "PositionSmoothing", 8f, 0f, 120f, "Camera position follow speed. Lower values are smoother; zero snaps instantly.");
			TelemetryRefreshInterval = BindFloat("Telemetry", "RefreshInterval", 0.2f, 0.05f, 10f, "Seconds between scene evaluations.");
			VoiceLoudnessThreshold = BindFloat("Telemetry", "VoiceLoudnessThreshold", 0.01f, 0f, 1f, "Minimum squared voice level counted as active speech.");
			ThreatAwarenessRange = BindFloat("Telemetry", "ThreatAwarenessRange", 45f, 1f, 1000f, "Range used for hostile scoutmaster and zombie awareness.");
			LineOfSightChecks = ((BaseUnityPlugin)this).Config.Bind<bool>("Telemetry", "LineOfSightChecks", true, "Use physics line-of-sight checks for event subjects.");
			TargetSelectionEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("TargetSelection", "Enabled", true, "Allow automatic spectator target selection.");
			TargetSwitchMinInterval = BindFloat("TargetSelection", "SwitchMinInterval", 2.5f, 0f, 300f, "Minimum seconds between automatic target switches.");
			TargetSwitchScoreLead = BindFloat("TargetSelection", "SwitchScoreLead", 15f, 0f, 1000f, "Required score lead before switching target.");
			TargetStickinessBonus = BindFloat("TargetSelection", "StickinessBonus", 20f, 0f, 1000f, "Score bonus for the current target.");
			TargetMaxWatchSeconds = BindFloat("TargetSelection", "MaxWatchSeconds", 12f, 1f, 3600f, "Seconds before current-target stickiness fades.");
			NearbyPlayerRange = BindFloat("TargetSelection", "NearbyPlayerRange", 18f, 1f, 1000f, "Range used for social and rescue scene scoring.");
			TargetDebugScores = ((BaseUnityPlugin)this).Config.Bind<bool>("TargetSelection", "DebugScores", false, "Log score breakdowns when the target changes.");
			EventLookEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("EventLook", "Enabled", true, "Allow automatic multi-subject event framing.");
			EventLookFocusRange = BindFloat("EventLook", "FocusRange", 60f, 1f, 1000f, "Maximum distance for event-framing subjects.");
			EventLookMaxFrameAngle = BindFloat("EventLook", "MaxFrameAngle", 50f, 5f, 80f, "Maximum angular spread of a framed subject group.");
			EventLookSwitchScoreLead = BindFloat("EventLook", "SwitchScoreLead", 15f, 0f, 1000f, "Required score lead before changing the framed event.");
			EventLookMinHoldSeconds = BindFloat("EventLook", "MinHoldSeconds", 1.2f, 0f, 60f, "Minimum time to hold a framed event.");
			PlayerLookToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "PlayerLookToggleKey", new KeyboardShortcut((KeyCode)114, Array.Empty<KeyCode>()), "Toggle player-look camera.");
			TargetSelectionToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "TargetSelectionToggleKey", new KeyboardShortcut((KeyCode)97, Array.Empty<KeyCode>()), "Toggle automatic player selection.");
			EventLookToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "EventLookToggleKey", new KeyboardShortcut((KeyCode)102, Array.Empty<KeyCode>()), "Toggle event framing.");
			SmoothRotationToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "SmoothRotationToggleKey", new KeyboardShortcut((KeyCode)115, Array.Empty<KeyCode>()), "Toggle acceleration/deceleration camera rotation.");
			ConfigReloadKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "ConfigReloadKey", new KeyboardShortcut((KeyCode)108, Array.Empty<KeyCode>()), "Reload this mod's config file.");
		}

		private void Update()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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)
			//IL_00bd: 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_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			if (configReloader != null)
			{
				if (configReloader.Tick(Time.unscaledTime, out string error))
				{
					ShowStatus("Auto camera config: UPDATED", Color.green);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Configuration updated from disk.");
				}
				if (error != null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Live config reload: " + error));
				}
			}
			ApplyPendingSettings();
			bool flag = SpectatorControlsAllowed();
			if (!flag && wasSpectating)
			{
				CameraDirector.ResetRotation();
				SpectatorTelemetry.Clear();
				AutoTargetSelector.EndSession();
				EventFramer.Reset();
			}
			wasSpectating = flag;
			if (flag && NoGameUiCapturingInput())
			{
				KeyboardShortcut value = TargetSelectionToggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					AutoTargetSelector.ToggleByUser();
				}
				value = EventLookToggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					ToggleEventLook();
				}
				value = SmoothRotationToggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					ToggleSmoothRotation();
				}
				value = PlayerLookToggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					SetLookMode((LookMode != SpectatorLookMode.PlayerLook) ? SpectatorLookMode.PlayerLook : SpectatorLookMode.Off);
				}
				if (TryGetPitchAdjustment(out var delta))
				{
					AdjustPitch(delta);
				}
				if (TryGetHeightAdjustment(out var delta2))
				{
					CameraVerticalOffset.Value = Mathf.Clamp(CameraVerticalOffset.Value + delta2, -3f, 3f);
					ShowStatus("Camera height: " + FormatMeters(CameraVerticalOffset.Value), Color.cyan);
				}
				value = ConfigReloadKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					ReloadConfig();
				}
			}
		}

		private void OnGUI()
		{
			//IL_00cd: 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_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: 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)
			//IL_0130: 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_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_004d: Expected O, but got Unknown
			if (SpectatorControlsAllowed() && !(Time.unscaledTime >= overlayUntil) && !string.IsNullOrEmpty(overlayMessage))
			{
				if (overlayStyle == null)
				{
					overlayStyle = new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)4,
						fontStyle = (FontStyle)1
					};
				}
				overlayStyle.fontSize = Mathf.Clamp(Mathf.RoundToInt((float)Screen.height * 0.024f), 16, 26);
				float num = overlayUntil - Time.unscaledTime;
				float num2 = Mathf.Clamp01(Mathf.Min(1f, num / 0.3f));
				float num3 = Mathf.Min((float)Screen.width - 40f, 620f);
				Rect val = new Rect(((float)Screen.width - num3) * 0.5f, (float)Screen.height * 0.14f, num3, 52f);
				Color color = GUI.color;
				GUI.color = new Color(0f, 0f, 0f, 0.58f * num2);
				GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture);
				overlayStyle.normal.textColor = new Color(overlayColor.r, overlayColor.g, overlayColor.b, num2);
				GUI.color = Color.white;
				GUI.Label(val, overlayMessage, overlayStyle);
				GUI.color = color;
			}
		}

		internal static bool SpectatorControlsAllowed()
		{
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null && localCharacter.data.fullyPassedOut && (Object)(object)Singleton<MainCameraMovement>.Instance != (Object)null)
			{
				return MainCameraMovement.IsSpectating;
			}
			return false;
		}

		internal void ShowStatus(string message, Color color, float seconds = 1.4f)
		{
			//IL_0008: 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)
			overlayMessage = message;
			overlayColor = color;
			overlayUntil = Time.unscaledTime + Mathf.Max(0.1f, seconds);
		}

		internal void SetLookMode(SpectatorLookMode mode, bool showMessage = true)
		{
			//IL_004d: 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 (mode == SpectatorLookMode.EventFraming && !EventLookEnabled.Value)
			{
				mode = SpectatorLookMode.Off;
			}
			if (LookMode != mode)
			{
				LookMode = mode;
				CameraDirector.ResetRotation();
				EventFramer.Reset();
				if (showMessage)
				{
					ShowStatus("Auto camera: " + GetModeLabel(mode), (mode == SpectatorLookMode.Off) ? Color.yellow : Color.green);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Automatic spectator camera mode: " + GetModeLabel(mode) + "."));
			}
		}

		private static bool NoGameUiCapturingInput()
		{
			GUIManager instance = GUIManager.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (!instance.windowBlockingInput && !instance.wheelActive)
				{
					return !GUIManager.InPauseMenu;
				}
				return false;
			}
			return true;
		}

		private void ToggleEventLook()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!EventLookEnabled.Value)
			{
				ShowStatus("Event framing disabled in config", Color.yellow);
			}
			else
			{
				SetLookMode((LookMode != SpectatorLookMode.EventFraming) ? SpectatorLookMode.EventFraming : SpectatorLookMode.Off);
			}
		}

		private void ToggleSmoothRotation()
		{
			//IL_0044: 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)
			SmoothRotationEnabled = !SmoothRotationEnabled;
			CameraDirector.ResetRotationMotion();
			string text = (SmoothRotationEnabled ? "ON" : "OFF");
			ShowStatus("Smooth rotation: " + text, SmoothRotationEnabled ? Color.green : Color.yellow);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Smooth rotation mode: " + text + "."));
		}

		private void AdjustPitch(float delta)
		{
			//IL_0073: 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)
			ConfigEntry<float> val = (ConfigEntry<float>)(LookMode switch
			{
				SpectatorLookMode.PlayerLook => PlayerPitchDegrees, 
				SpectatorLookMode.EventFraming => EventPitchDegrees, 
				_ => null, 
			});
			if (val == null)
			{
				ShowStatus("Enable an automatic camera mode first", Color.yellow);
				return;
			}
			val.Value = Mathf.Clamp(val.Value + delta, -45f, 45f);
			ShowStatus($"Camera pitch: {val.Value:+0.##;-0.##;0} degrees", Color.cyan);
		}

		private void ReloadConfig()
		{
			//IL_0046: 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)
			try
			{
				configReloader?.ReloadNow();
				ApplyPendingSettings();
				ShowStatus("Auto camera config: RELOADED", Color.green);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Configuration reloaded by user.");
			}
			catch (Exception arg)
			{
				ShowStatus("Auto camera config: RELOAD FAILED", Color.red);
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"Configuration reload failed: {arg}");
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			Interlocked.Exchange(ref settingsChanged, 1);
		}

		private void ApplyPendingSettings()
		{
			if (Interlocked.Exchange(ref settingsChanged, 0) != 0)
			{
				SanitizeNumbers();
				if (!EventLookEnabled.Value && LookMode == SpectatorLookMode.EventFraming)
				{
					SetLookMode(SpectatorLookMode.PlayerLook, showMessage: false);
				}
				CameraDirector.ResetRotationMotion();
				SpectatorTelemetry.RequestRefresh();
				EventFramer.Reset();
			}
		}

		private void SanitizeNumbers()
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			try
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
				foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in ((BaseUnityPlugin)this).Config)
				{
					if (item.Value.BoxedValue is float f && (float.IsNaN(f) || float.IsInfinity(f)))
					{
						item.Value.BoxedValue = item.Value.DefaultValue;
					}
				}
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private ConfigEntry<float> BindFloat(string section, string key, float value, float minimum, float maximum, string description)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<float>(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(minimum, maximum), Array.Empty<object>()));
		}

		private static bool TryGetPitchAdjustment(out float delta)
		{
			delta = 0f;
			if (Input.GetKeyDown((KeyCode)270) || Input.GetKeyDown((KeyCode)43) || Input.GetKeyDown((KeyCode)61))
			{
				delta = 2.5f;
				return true;
			}
			if (Input.GetKeyDown((KeyCode)269) || Input.GetKeyDown((KeyCode)45))
			{
				delta = -2.5f;
				return true;
			}
			return false;
		}

		private static bool TryGetHeightAdjustment(out float delta)
		{
			delta = 0f;
			if (Input.GetKeyDown((KeyCode)57) || Input.GetKeyDown((KeyCode)265))
			{
				delta = 0.25f;
				return true;
			}
			if (Input.GetKeyDown((KeyCode)54) || Input.GetKeyDown((KeyCode)262))
			{
				delta = -0.25f;
				return true;
			}
			return false;
		}

		private static string GetModeLabel(SpectatorLookMode mode)
		{
			return mode switch
			{
				SpectatorLookMode.PlayerLook => "PLAYER LOOK", 
				SpectatorLookMode.EventFraming => "EVENT FRAMING", 
				_ => "OFF", 
			};
		}

		private static string FormatMeters(float meters)
		{
			if (Mathf.Abs(meters) < 0.001f)
			{
				return "0";
			}
			return string.Format("{0:0.##} {1}", Mathf.Abs(meters), (meters > 0f) ? "up" : "down");
		}
	}
	internal enum SpectatorLookMode
	{
		Off,
		PlayerLook,
		EventFraming
	}
	[HarmonyPatch(typeof(MainCameraMovement), "Spectate")]
	internal static class MainCameraMovementSpectatePatch
	{
		private static void Prefix(bool useGhost)
		{
			if (useGhost)
			{
				SpectatorSession.PreserveLocalTarget();
				SpectatorTelemetry.Tick();
				AutoTargetSelector.Apply();
			}
		}

		private static void Postfix(MainCameraMovement __instance, bool useGhost)
		{
			if (useGhost)
			{
				CameraDirector.Apply(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(MainCameraMovement), "SwapSpecPlayerLeft")]
	internal static class ManualSpectateLeftPatch
	{
		private static bool Prefix()
		{
			if (SpectatorSession.MustWatchSelf)
			{
				return false;
			}
			AutoTargetSelector.OnManualSwitch();
			return true;
		}
	}
	[HarmonyPatch(typeof(MainCameraMovement), "SwapSpecPlayerRight")]
	internal static class ManualSpectateRightPatch
	{
		private static bool Prefix()
		{
			if (SpectatorSession.MustWatchSelf)
			{
				return false;
			}
			AutoTargetSelector.OnManualSwitch();
			return true;
		}
	}
	internal static class SceneEvaluator
	{
		internal static SceneScore Evaluate(PlayerSnapshot player, IReadOnlyList<PlayerSnapshot> players, SceneSignals signals)
		{
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			Plugin instance = Plugin.Instance;
			float num = Mathf.Max(5f, instance.ThreatAwarenessRange.Value);
			float num2 = (IsFinite(player.NearestThreatDistance) ? (1f - Mathf.Clamp01((player.NearestThreatDistance - 3f) / (num - 3f))) : 0f);
			float num3 = Mathf.Clamp01((float)player.Threats.Count / 3f);
			float num4 = Mathf.Clamp01((float)player.VisibleThreatCount / 2f);
			float num5 = 58f * num2 + 30f * num3 + 24f * num4 + (signals.ThreatAppeared ? 28f : 0f) + 22f * signals.ThreatClosingIn;
			for (int i = 0; i < player.Threats.Count; i++)
			{
				ThreatSnapshot threatSnapshot = player.Threats[i];
				if (threatSnapshot.IsScoutmaster)
				{
					num5 += 26f * (1f - Mathf.Clamp01(threatSnapshot.Distance / num));
				}
				else if (threatSnapshot.IsZombie)
				{
					num5 += 16f * (1f - Mathf.Clamp01(threatSnapshot.Distance / num));
				}
			}
			float num6 = 0f;
			if (player.IsPassedOut)
			{
				num6 = 90f;
			}
			else if (player.IsFalling)
			{
				num6 = 45f + 35f * Mathf.Clamp01((0f - player.Velocity.y - 4f) / 14f);
			}
			if (player.IsClimbing)
			{
				num6 = Mathf.Max(num6, 22f + 52f * (1f - Mathf.Clamp01(player.Stamina / 0.45f)));
			}
			num6 += 22f * Mathf.Clamp01((player.Affliction - 0.55f) / 0.45f);
			if (signals.FallStarted)
			{
				num6 += 24f;
			}
			if (signals.PassedOutStarted)
			{
				num6 += 45f;
			}
			float num7 = 0f;
			if (player.IsVoiceActive(instance.VoiceLoudnessThreshold.Value))
			{
				num7 += 38f + 18f * VoiceStrength(player, instance.VoiceLoudnessThreshold.Value);
			}
			num7 += 14f * Mathf.Clamp01((float)player.NearbyPlayerCount / 2f);
			num7 += 24f * Mathf.Clamp01((float)player.NearbyTalkingPlayerCount / 2f);
			if (player.IsCarrying || player.IsCarried)
			{
				num7 += 28f;
			}
			if (num5 >= 70f)
			{
				num7 *= 0.2f;
			}
			else if (num5 >= 35f)
			{
				num7 *= 0.55f;
			}
			float num8 = 0f;
			if (player.IsFalling || player.IsJumping)
			{
				num8 += 25f;
			}
			else if (player.IsClimbing)
			{
				num8 += 20f;
			}
			else if (player.IsSprinting)
			{
				num8 += 13f;
			}
			else if (player.Speed >= 1.5f)
			{
				num8 += 8f;
			}
			if (player.IsUsingItem)
			{
				num8 += 24f;
			}
			else if ((Object)(object)player.HeldItem != (Object)null)
			{
				num8 += 5f;
			}
			float framePotential = GetFramePotential(player, players, instance);
			float num9 = SceneScoring.Combine(num5, num6, num7, num8, framePotential);
			return new SceneScore(num5, num6, num7, num8, framePotential, num9, num9, 0f, 0f);
		}

		internal static float GetEventPlayerScore(PlayerSnapshot player, float distance, float focusRange)
		{
			Plugin instance = Plugin.Instance;
			float num = 10f + 12f * (1f - Mathf.Clamp01(distance / Mathf.Max(1f, focusRange)));
			if (player.IsVoiceActive(instance.VoiceLoudnessThreshold.Value))
			{
				num += 34f * Mathf.Max(0.4f, VoiceStrength(player, instance.VoiceLoudnessThreshold.Value));
			}
			if (player.IsPassedOut || player.IsFalling)
			{
				num += 30f;
			}
			else if (player.IsClimbing || player.IsJumping)
			{
				num += 18f;
			}
			else if (player.IsSprinting)
			{
				num += 10f;
			}
			if (player.IsCarrying || player.IsCarried)
			{
				num += 22f;
			}
			if (player.IsUsingItem)
			{
				num += 18f;
			}
			return num;
		}

		internal static float GetEventThreatScore(ThreatSnapshot threat, float focusRange)
		{
			float num = 25f + 35f * (1f - Mathf.Clamp01(threat.Distance / Mathf.Max(1f, focusRange)));
			if (threat.Visible)
			{
				num += 18f;
			}
			if (threat.IsScoutmaster)
			{
				num += 42f;
			}
			else if (threat.IsZombie)
			{
				num += 28f;
			}
			return num;
		}

		private static float GetFramePotential(PlayerSnapshot player, IReadOnlyList<PlayerSnapshot> players, Plugin plugin)
		{
			//IL_00a0: 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)
			if (!plugin.EventLookEnabled.Value)
			{
				return 0f;
			}
			float num = Mathf.Max(1f, plugin.EventLookFocusRange.Value);
			float num2 = 0f;
			int num3 = 0;
			for (int i = 0; i < player.Threats.Count; i++)
			{
				ThreatSnapshot threat = player.Threats[i];
				if (threat.Distance <= num)
				{
					num2 = Mathf.Max(num2, GetEventThreatScore(threat, num) * 0.55f);
					num3++;
				}
			}
			for (int j = 0; j < players.Count; j++)
			{
				PlayerSnapshot playerSnapshot = players[j];
				if (playerSnapshot.StableId != player.StableId && playerSnapshot.IsSelectable)
				{
					float num4 = Vector3.Distance(player.Position, playerSnapshot.Position);
					if (num4 <= num)
					{
						num2 = Mathf.Max(num2, GetEventPlayerScore(playerSnapshot, num4, num) * 0.45f);
						num3++;
					}
				}
			}
			if ((Object)(object)player.HeldItem != (Object)null)
			{
				num2 += (player.IsUsingItem ? 16f : 6f);
				num3++;
			}
			num2 += Mathf.Min(28f, (float)Mathf.Max(0, num3 - 1) * 7f);
			return Mathf.Min(70f, num2);
		}

		private static float VoiceStrength(PlayerSnapshot player, float threshold)
		{
			float num = Mathf.Max(0.0001f, threshold);
			return Mathf.Clamp01((player.VoiceLevel - num) / Mathf.Max(0.04f, num * 4f));
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal readonly struct SceneSignals
	{
		internal bool ThreatAppeared { get; }

		internal float ThreatClosingIn { get; }

		internal bool FallStarted { get; }

		internal bool PassedOutStarted { get; }

		internal SceneSignals(bool threatAppeared, float threatClosingIn, bool fallStarted, bool passedOutStarted)
		{
			ThreatAppeared = threatAppeared;
			ThreatClosingIn = threatClosingIn;
			FallStarted = fallStarted;
			PassedOutStarted = passedOutStarted;
		}
	}
	internal readonly struct SceneScore
	{
		internal float Threat { get; }

		internal float Peril { get; }

		internal float Social { get; }

		internal float Activity { get; }

		internal float FramePotential { get; }

		internal float Scene { get; }

		internal float SmoothedScene { get; }

		internal float CurrentBonus { get; }

		internal float RecentlyWatchedPenalty { get; }

		internal float Final { get; }

		internal SceneScore(float threat, float peril, float social, float activity, float framePotential, float scene, float smoothedScene, float currentBonus, float recentlyWatchedPenalty)
		{
			Threat = threat;
			Peril = peril;
			Social = social;
			Activity = activity;
			FramePotential = framePotential;
			Scene = scene;
			SmoothedScene = smoothedScene;
			CurrentBonus = currentBonus;
			RecentlyWatchedPenalty = recentlyWatchedPenalty;
			Final = smoothedScene + currentBonus - recentlyWatchedPenalty;
		}

		internal SceneScore WithFinal(float smoothedScene, float currentBonus, float recentlyWatchedPenalty)
		{
			return new SceneScore(Threat, Peril, Social, Activity, FramePotential, Scene, smoothedScene, currentBonus, recentlyWatchedPenalty);
		}
	}
	internal static class SceneScoring
	{
		internal static float Combine(float threat, float peril, float social, float activity, float framePotential)
		{
			float num = threat * 0.18f + peril * 0.22f + social * 0.25f + activity * 0.3f + framePotential * 0.18f;
			return Mathf.Max(Mathf.Max(threat + Mathf.Min(30f, num - threat * 0.18f), peril + Mathf.Min(30f, num - peril * 0.22f)), Mathf.Max(social + Mathf.Min(30f, num - social * 0.25f), activity + Mathf.Min(30f, num - activity * 0.3f)));
		}
	}
	internal static class SpectatorRules
	{
		internal static bool MustWatchSelf(bool fullyPassedOut, bool dead)
		{
			if (fullyPassedOut)
			{
				return !dead;
			}
			return false;
		}

		internal static bool CanSelectOthers(bool fullyPassedOut, bool dead, bool canSpectateLocalBody)
		{
			if (fullyPassedOut && dead)
			{
				return !canSpectateLocalBody;
			}
			return false;
		}
	}
	internal static class SpectatorSession
	{
		private static readonly Action<Character> SetTarget = CreateTargetSetter();

		internal static bool MustWatchSelf
		{
			get
			{
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null)
				{
					return SpectatorRules.MustWatchSelf(localCharacter.data.fullyPassedOut, localCharacter.data.dead);
				}
				return false;
			}
		}

		internal static bool CanSelectOthers
		{
			get
			{
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.data != (Object)null)
				{
					return SpectatorRules.CanSelectOthers(localCharacter.data.fullyPassedOut, localCharacter.data.dead, localCharacter.data.canBeSpectated);
				}
				return false;
			}
		}

		private static Action<Character> CreateTargetSetter()
		{
			MethodInfo methodInfo = typeof(MainCameraMovement).GetProperty("specCharacter", BindingFlags.Static | BindingFlags.Public)?.GetSetMethod(nonPublic: true);
			if (methodInfo == null)
			{
				throw new MissingMethodException("PEAK spectator target setter was not found.");
			}
			return (Action<Character>)Delegate.CreateDelegate(typeof(Action<Character>), methodInfo);
		}

		internal static void PreserveLocalTarget()
		{
			if (MustWatchSelf && (Object)(object)MainCameraMovement.specCharacter != (Object)(object)Character.localCharacter)
			{
				SetTarget(Character.localCharacter);
			}
		}

		internal static bool TrySelect(Character? target)
		{
			if (!CanSelectOthers || !SpectatorTelemetry.IsSelectableNow(target))
			{
				return false;
			}
			SetTarget(target);
			return (Object)(object)MainCameraMovement.specCharacter == (Object)(object)target;
		}
	}
	internal static class SpectatorTelemetry
	{
		private static readonly List<PlayerSnapshot> PlayerSnapshots = new List<PlayerSnapshot>();

		private static readonly Dictionary<int, PlayerSnapshot> PlayersById = new Dictionary<int, PlayerSnapshot>();

		private static float nextRefreshTime;

		internal static IReadOnlyList<PlayerSnapshot> Players => PlayerSnapshots;

		internal static int Version { get; private set; }

		internal static void Tick()
		{
			Plugin instance = Plugin.Instance;
			if (!Plugin.SpectatorControlsAllowed() || !SpectatorSession.CanSelectOthers || ((!AutoTargetSelector.Active || !instance.TargetSelectionEnabled.Value) && (instance.LookMode != SpectatorLookMode.EventFraming || !instance.EventLookEnabled.Value)))
			{
				if (PlayerSnapshots.Count > 0)
				{
					Clear();
				}
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!(unscaledTime < nextRefreshTime))
			{
				nextRefreshTime = unscaledTime + Mathf.Max(0.05f, Plugin.Instance.TelemetryRefreshInterval.Value);
				Refresh();
			}
		}

		internal static bool TryGet(Character? character, out PlayerSnapshot? snapshot)
		{
			snapshot = null;
			if ((Object)(object)character == (Object)null)
			{
				return false;
			}
			int stableId = GetStableId(character);
			if (stableId != 0)
			{
				return PlayersById.TryGetValue(stableId, out snapshot);
			}
			return false;
		}

		internal static bool TryGet(int stableId, out PlayerSnapshot? snapshot)
		{
			return PlayersById.TryGetValue(stableId, out snapshot);
		}

		internal static void Clear()
		{
			PlayerSnapshots.Clear();
			PlayersById.Clear();
			nextRefreshTime = 0f;
			Version++;
		}

		internal static void RequestRefresh()
		{
			nextRefreshTime = 0f;
		}

		internal static bool IsSelectableNow(Character? character)
		{
			if ((Object)(object)character != (Object)null && (Object)(object)character != (Object)(object)Character.localCharacter && ((Component)character).gameObject.activeInHierarchy && !character.isBot && (Object)(object)character.data != (Object)null && !character.data.dead)
			{
				return character.data.canBeSpectated;
			}
			return false;
		}

		internal static bool HasLineOfSight(Vector3 origin, Vector3 target, Component? targetComponent = null)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//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_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_001d: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = target - origin;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude <= 0.05f)
			{
				return true;
			}
			RaycastHit val2 = default(RaycastHit);
			if (!Physics.Raycast(origin, val / magnitude, ref val2, magnitude, -5, (QueryTriggerInteraction)1))
			{
				return true;
			}
			if ((Object)(object)targetComponent == (Object)null || (Object)(object)((RaycastHit)(ref val2)).collider == (Object)null)
			{
				return false;
			}
			Transform transform = ((Component)((RaycastHit)(ref val2)).collider).transform;
			Transform transform2 = targetComponent.transform;
			if (!((Object)(object)transform == (Object)(object)transform2))
			{
				return transform.IsChildOf(transform2);
			}
			return true;
		}

		private static void Refresh()
		{
			PlayerSnapshots.Clear();
			PlayersById.Clear();
			List<Character> allPlayerCharacters;
			try
			{
				allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not enumerate PEAK players: " + ex.Message));
				Version++;
				return;
			}
			for (int i = 0; i < allPlayerCharacters.Count; i++)
			{
				Character val = allPlayerCharacters[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.data == (Object)null))
				{
					PlayerSnapshot playerSnapshot = CreatePlayerSnapshot(val);
					if (playerSnapshot.StableId != 0)
					{
						PlayerSnapshots.Add(playerSnapshot);
						PlayersById[playerSnapshot.StableId] = playerSnapshot;
					}
				}
			}
			AddRelationships();
			AddThreats();
			Version++;
		}

		private static PlayerSnapshot CreatePlayerSnapshot(Character character)
		{
			//IL_000a: 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_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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: 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_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			CharacterData data = character.data;
			Vector3 livePosition = GetLivePosition(character);
			Vector3 liveHeadPosition = GetLiveHeadPosition(character, livePosition);
			Vector3 avarageVelocity = data.avarageVelocity;
			Item currentItem = data.currentItem;
			float voiceLevel = 0f;
			try
			{
				if (character.refs != null && (Object)(object)character.refs.voice != (Object)null)
				{
					voiceLevel = Mathf.Max(0f, character.refs.voice.LastReadSquaredMax);
				}
			}
			catch (Exception)
			{
				voiceLevel = 0f;
			}
			float stamina = Mathf.Max(0f, data.currentStamina + data.extraStamina);
			float affliction = 0f;
			try
			{
				if (character.refs != null && (Object)(object)character.refs.afflictions != (Object)null)
				{
					affliction = Mathf.Clamp01(character.refs.afflictions.statusSum);
				}
			}
			catch (Exception)
			{
				affliction = 0f;
			}
			bool isFalling = data.fallSeconds > 0.1f || (!data.isGrounded && avarageVelocity.y < -6f);
			bool isUsingItem = (Object)(object)currentItem != (Object)null && (currentItem.isUsingPrimary || currentItem.isUsingSecondary || currentItem.consuming);
			return new PlayerSnapshot(character, GetStableId(character), GetPl