Decompiled source of AutoCameraSpectator v1.0.5
BepInEx/plugins/AutoCameraSpectator/AutoCameraSpectator.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
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