using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LiveSpectate")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1393328e73447401cf7c63b113aadae58e5969be")] [assembly: AssemblyProduct("LiveSpectate")] [assembly: AssemblyTitle("LiveSpectate")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LiveSpectate { internal readonly struct LiveSpectateCameraCloneResult { internal GameObject RootObject { get; } internal Camera RootCamera { get; } internal LiveSpectateCameraCloneResult(GameObject rootObject, Camera rootCamera) { RootObject = rootObject; RootCamera = rootCamera; } } internal static class LiveSpectateCameraFactory { private const string PostProcessLayerTypeName = "UnityEngine.Rendering.PostProcessing.PostProcessLayer"; internal static LiveSpectateCameraCloneResult Create(Camera source, float liveFieldOfView) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown GameObject val = new GameObject("LiveSpectate Local Camera"); Object.DontDestroyOnLoad((Object)(object)val); Camera val2 = val.AddComponent(); CopyCameraCoreFields(source, val2, liveFieldOfView, source.depth + 0.01f); if ((Object)(object)source.targetTexture != (Object)null) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogInfo((object)("LiveSpectate source main camera has non-null targetTexture '" + ((Object)source.targetTexture).name + "'; it is intentionally copied/shared to keep RenderTextureMain output updating during live spectate.")); } } DumpSourceCameraComponentDiagnostics(source); CopyPostProcessLayerIfPresent(source, val2); int num = CloneChildCameras(source, val, val2); LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate cloned {num} child camera(s) from original main camera hierarchy."); return new LiveSpectateCameraCloneResult(val, val2); } private static int CloneChildCameras(Camera source, GameObject liveCameraObject, Camera liveCamera) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || (Object)(object)liveCameraObject == (Object)null) { return 0; } int num = 0; float num2 = (((Object)(object)liveCamera != (Object)null) ? (liveCamera.depth - source.depth) : 0f); Camera[] componentsInChildren = ((Component)source).GetComponentsInChildren(true); foreach (Camera val in componentsInChildren) { if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)source) { continue; } GameObject val2 = new GameObject("LiveSpectate " + ((Object)val).name); val2.transform.SetParent(liveCameraObject.transform, false); val2.transform.localPosition = ((Component)source).transform.InverseTransformPoint(((Component)val).transform.position); val2.transform.localRotation = Quaternion.Inverse(((Component)source).transform.rotation) * ((Component)val).transform.rotation; val2.transform.localScale = GetRelativeLossyScale(((Component)val).transform, ((Component)source).transform); Camera val3 = val2.AddComponent(); CopyCameraCoreFields(val, val3, val.fieldOfView, val.depth + num2); ((Behaviour)val3).enabled = ((Behaviour)val).enabled; CopyPostProcessLayerIfPresent(val, val3); num++; ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate cloned child camera '{((Object)val).name}' -> '{((Object)val3).name}' fov={val3.fieldOfView:0.###}, depth={val3.depth:0.###}, cullingMask={val3.cullingMask}, clearFlags={val3.clearFlags}, enabled={((Behaviour)val3).enabled}."); if ((Object)(object)val.targetTexture != (Object)null) { LiveSpectatePlugin.Log.LogInfo((object)("LiveSpectate source child camera '" + ((Object)val).name + "' has non-null targetTexture '" + ((Object)val.targetTexture).name + "'; it is intentionally copied/shared to cloned child camera '" + ((Object)val3).name + "' to keep RenderTextureMain output updating during live spectate.")); } } } return num; } private static Vector3 GetRelativeLossyScale(Transform child, Transform root) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)child == (Object)null || (Object)(object)root == (Object)null) { return Vector3.one; } Vector3 lossyScale = child.lossyScale; Vector3 lossyScale2 = root.lossyScale; Vector3 localScale = child.localScale; return new Vector3(SafeDivideScale(lossyScale.x, lossyScale2.x, localScale.x), SafeDivideScale(lossyScale.y, lossyScale2.y, localScale.y), SafeDivideScale(lossyScale.z, lossyScale2.z, localScale.z)); } private static float SafeDivideScale(float childScale, float rootScale, float fallback) { if (!LiveSpectateMath.IsFinite(childScale) || !LiveSpectateMath.IsFinite(rootScale) || Mathf.Approximately(rootScale, 0f)) { return LiveSpectateMath.IsFinite(fallback) ? fallback : 1f; } float num = childScale / rootScale; return LiveSpectateMath.IsFinite(num) ? num : (LiveSpectateMath.IsFinite(fallback) ? fallback : 1f); } private static void CopyCameraCoreFields(Camera source, Camera destination, float fieldOfView, float depth) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) destination.targetTexture = source.targetTexture; destination.cullingMask = source.cullingMask; destination.clearFlags = source.clearFlags; destination.backgroundColor = source.backgroundColor; destination.nearClipPlane = source.nearClipPlane; destination.farClipPlane = source.farClipPlane; destination.fieldOfView = fieldOfView; destination.depth = depth; destination.renderingPath = source.renderingPath; destination.allowHDR = source.allowHDR; destination.allowMSAA = source.allowMSAA; destination.useOcclusionCulling = source.useOcclusionCulling; destination.stereoTargetEye = source.stereoTargetEye; } private static void CopyPostProcessLayerIfPresent(Camera source, Camera destination) { Type type = LiveSpectateReflection.FindType("UnityEngine.Rendering.PostProcessing.PostProcessLayer"); if (type == null) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer copy skipped; type not found."); } return; } Component component = ((Component)source).GetComponent(type); if ((Object)(object)component == (Object)null) { ConfigEntry verboseDiagnostics2 = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics2 != null && verboseDiagnostics2.Value) { LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer copy skipped; source direct component not found."); } return; } Component val = null; try { val = ((Component)destination).gameObject.AddComponent(type); Behaviour val2 = (Behaviour)(object)((component is Behaviour) ? component : null); bool enabled = val2 != null && val2.enabled; Behaviour val3 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val3 != null) { val3.enabled = false; } if (!TryCopyComponentWithJson(component, val)) { Object.DestroyImmediate((Object)(object)val); LiveSpectatePlugin.Log.LogWarning((object)"LiveSpectate PostProcessLayer copy skipped; serialized copy failed."); return; } NormalizePostProcessLayerVolumeTrigger(source, destination, val); Behaviour val4 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val4 != null) { val4.enabled = enabled; } LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate copied direct PostProcessLayer to live camera."); } catch (Exception ex) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate PostProcessLayer copy failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static bool TryCopyComponentWithJson(Component source, Component destination) { try { Type type = LiveSpectateReflection.FindType("UnityEngine.JsonUtility"); MethodInfo methodInfo = type?.GetMethod("ToJson", new Type[1] { typeof(object) }); MethodInfo methodInfo2 = type?.GetMethod("FromJsonOverwrite", new Type[2] { typeof(string), typeof(object) }); if (methodInfo == null || methodInfo2 == null) { return false; } string text = methodInfo.Invoke(null, new object[1] { source }) as string; if (string.IsNullOrEmpty(text)) { return false; } methodInfo2.Invoke(null, new object[2] { text, destination }); return true; } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("PostProcessLayer JsonUtility copy failed: " + ex.GetType().Name + ": " + ex.Message)); } return false; } } private static void NormalizePostProcessLayerVolumeTrigger(Camera source, Camera destination, Component destinationLayer) { if ((Object)(object)source == (Object)null || (Object)(object)destination == (Object)null || (Object)(object)destinationLayer == (Object)null) { return; } object memberValue = LiveSpectateReflection.GetMemberValue(destinationLayer, "volumeTrigger"); if ((memberValue == null || memberValue == ((Component)source).transform) && LiveSpectateReflection.TrySetMemberValue(destinationLayer, "volumeTrigger", ((Component)destination).transform)) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogInfo((object)"LiveSpectate PostProcessLayer volumeTrigger normalized to live camera transform."); } } } private static void DumpSourceCameraComponentDiagnostics(Camera source) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics == null || !verboseDiagnostics.Value || (Object)(object)source == (Object)null) { return; } try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("LiveSpectate source camera components: "); AppendComponentTypeNames(stringBuilder, ((Component)source).GetComponents()); Camera[] componentsInChildren = ((Component)source).GetComponentsInChildren(true); stringBuilder.Append(" | child cameras: "); for (int i = 0; i < componentsInChildren.Length; i++) { Camera val = componentsInChildren[i]; if (!((Object)(object)val == (Object)null)) { if (i > 0) { stringBuilder.Append("; "); } stringBuilder.Append(((Object)val).name); stringBuilder.Append(" ["); AppendComponentTypeNames(stringBuilder, ((Component)val).GetComponents()); stringBuilder.Append(']'); } } LiveSpectatePlugin.Log.LogInfo((object)stringBuilder.ToString()); } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate camera component diagnostics failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void AppendComponentTypeNames(StringBuilder builder, Component[] components) { if (components == null || components.Length == 0) { builder.Append(""); return; } for (int i = 0; i < components.Length; i++) { if (i > 0) { builder.Append(", "); } Component val = components[i]; builder.Append(((Object)(object)val != (Object)null) ? ((object)val).GetType().FullName : ""); } } } internal sealed class LiveSpectateCameraFollower { private const float DefaultDistance = 3f; private const float MinDistance = 1f; private const float MaxDistance = 6f; private const float ScrollDistanceMultiplier = 0.0025f; private const float DistanceSmoothing = 5f; private const float FocusSmoothing = 10f; private const float VehicleFocusSmoothing = 20f; private const float PitchClamp = 70f; private float yaw; private float pitch; private float cameraDistance = 3f; private Vector3 smoothedPivotPosition; private Quaternion smoothedPivotRotation = Quaternion.identity; private float smoothedLocalDistance = 3f; internal void Initialize(Camera liveCamera, Quaternion basis, Vector3 focus) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) yaw = ((Quaternion)(ref basis)).eulerAngles.y; pitch = NormalizePitch(((Quaternion)(ref basis)).eulerAngles.x); cameraDistance = 3f; smoothedPivotPosition = focus; smoothedPivotRotation = basis; smoothedLocalDistance = 3f; ApplyLiveCameraTransform(liveCamera, smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance, smoothedPivotRotation); } internal void Update(Camera liveCamera, PlayerAvatar targetPlayer) { //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) float num = SemiFunc.InputMouseX(); float num2 = SemiFunc.InputMouseY(); float num3 = SemiFunc.InputScrollY(); if (!LiveSpectateMath.IsFinite(num)) { num = 0f; } if (!LiveSpectateMath.IsFinite(num2)) { num2 = 0f; } if (!LiveSpectateMath.IsFinite(num3)) { num3 = 0f; } if ((Object)(object)CameraAim.Instance != (Object)null && LiveSpectateReflection.GetBoolMember(CameraAim.Instance, "overrideAimStop")) { num = 0f; num2 = 0f; num3 = 0f; } float num4 = (((Object)(object)CameraAim.Instance != (Object)null) ? CameraAim.Instance.AimSpeedMouse : 1f); float num5 = (((Object)(object)GameplayManager.instance != (Object)null && LiveSpectateReflection.GetBoolMember(GameplayManager.instance, "aimInvertVertical")) ? (-1f) : 1f); yaw += num * num4 * 1.5f; pitch += (0f - num2) * num4 * 1.5f * num5; pitch = Mathf.Clamp(pitch, -70f, 70f); if (yaw > 360f) { yaw -= 360f; } else if (yaw < -360f) { yaw += 360f; } if (!LiveSpectateMath.IsFinite(cameraDistance)) { cameraDistance = 3f; } cameraDistance = Mathf.Clamp(cameraDistance - num3 * 0.0025f, 1f, 6f); if (!LiveSpectateMath.IsFinite(cameraDistance)) { cameraDistance = 3f; } Quaternion rotation = Quaternion.Euler(pitch, yaw, 0f); Vector3 position = GetTargetFocusPoint(targetPlayer); if (!LiveSpectateMath.IsFinite(position) || !LiveSpectateMath.IsFinite(rotation)) { GetFallbackCameraTransform(liveCamera, out position, out rotation); smoothedPivotPosition = position; smoothedPivotRotation = rotation; yaw = ((Quaternion)(ref rotation)).eulerAngles.y; pitch = NormalizePitch(((Quaternion)(ref rotation)).eulerAngles.x); } float num6 = (IsPlayerInVehicle(targetPlayer) ? 20f : 10f); float num7 = Mathf.Clamp01(Time.deltaTime * num6); smoothedPivotPosition = Vector3.Lerp(smoothedPivotPosition, position, num7); float num8 = (((Object)(object)GameplayManager.instance != (Object)null) ? LiveSpectateReflection.GetFloatMember(GameplayManager.instance, "cameraSmoothing", 0f) : 0f); float num9 = Mathf.Lerp(50f, 6.25f, Mathf.Clamp01(num8 / 100f)); float num10 = Mathf.Clamp01(Time.deltaTime * num9); smoothedPivotRotation = Quaternion.Lerp(smoothedPivotRotation, rotation, num10); if (!LiveSpectateMath.IsFinite(smoothedPivotPosition) || !LiveSpectateMath.IsFinite(smoothedPivotRotation)) { GetFallbackCameraTransform(liveCamera, out smoothedPivotPosition, out smoothedPivotRotation); } float num11 = ResolveCameraDistance(smoothedPivotPosition, smoothedPivotRotation, cameraDistance); smoothedLocalDistance = Mathf.Lerp(smoothedLocalDistance, num11, Mathf.Clamp01(Time.deltaTime * 5f)); Vector3 position2 = smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance; ApplyLiveCameraTransform(liveCamera, position2, smoothedPivotRotation); } internal void ResetForTarget(PlayerAvatar target, Camera liveCamera, bool applyTransform) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) Vector3 targetFocusPoint = GetTargetFocusPoint(target); smoothedPivotPosition = targetFocusPoint; smoothedPivotRotation = Quaternion.Euler(pitch, yaw, 0f); smoothedLocalDistance = Mathf.Clamp(cameraDistance, 1f, 6f); if (applyTransform && (Object)(object)liveCamera != (Object)null) { Vector3 position = smoothedPivotPosition - smoothedPivotRotation * Vector3.forward * smoothedLocalDistance; ApplyLiveCameraTransform(liveCamera, position, smoothedPivotRotation); } } internal void Reset() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) smoothedPivotPosition = Vector3.zero; smoothedPivotRotation = Quaternion.identity; smoothedLocalDistance = 3f; } internal static Vector3 GetTargetFocusPoint(PlayerAvatar player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } try { ItemVehicle vehicleForPlayer = ItemVehicle.GetVehicleForPlayer(player); Transform vehicleMeshTransform = GetVehicleMeshTransform(vehicleForPlayer); if ((Object)(object)vehicleMeshTransform != (Object)null) { return vehicleMeshTransform.position + Vector3.up * 0.8f; } } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("Vehicle focus lookup failed: " + ex.GetType().Name)); } } if ((Object)(object)player.spectatePoint != (Object)null) { return player.spectatePoint.position; } return ((Component)player).transform.position + Vector3.up * 1.6f; } private static float ResolveCameraDistance(Vector3 pivotPosition, Quaternion pivotRotation, float distance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!LiveSpectateMath.IsFinite(pivotPosition) || !LiveSpectateMath.IsFinite(pivotRotation)) { return GetSafeCameraDistance(distance); } Vector3 direction = -(pivotRotation * Vector3.forward); float distance2 = GetSafeCameraDistance(distance); try { LayerMask val = SemiFunc.LayerMaskGetVisionObstruct(); distance2 = ResolveCameraDistanceWithMask(pivotPosition, direction, distance2, LayerMask.op_Implicit(val)); } catch { try { distance2 = ResolveCameraDistanceWithMask(pivotPosition, direction, distance2, -5); } catch { distance2 = GetSafeCameraDistance(distance); } } return GetSafeCameraDistance(distance2); } private static float ResolveCameraDistanceWithMask(Vector3 origin, Vector3 direction, float distance, int layerMask) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) float num = GetSafeCameraDistance(distance); if (!LiveSpectateMath.IsFinite(origin) || !LiveSpectateMath.IsFinite(direction) || !LiveSpectateMath.IsFinite(num)) { return num; } RaycastHit[] array = Physics.SphereCastAll(origin, 0.1f, direction, num, layerMask, (QueryTriggerInteraction)1); RaycastHit[] array2 = array; for (int i = 0; i < array2.Length; i++) { RaycastHit val = array2[i]; if (!((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) && !(((RaycastHit)(ref val)).distance <= 0f) && !ShouldIgnoreCameraCollision(((RaycastHit)(ref val)).collider)) { num = Mathf.Min(num, Mathf.Max(1f, ((RaycastHit)(ref val)).distance)); } } return GetSafeCameraDistance(num); } private static float GetSafeCameraDistance(float distance) { return LiveSpectateMath.IsFinite(distance) ? Mathf.Clamp(distance, 1f, 6f) : 3f; } private static bool ShouldIgnoreCameraCollision(Collider collider) { Transform val = ((Component)collider).transform; while ((Object)(object)val != (Object)null) { GameObject gameObject = ((Component)val).gameObject; if ((Object)(object)gameObject.GetComponent() != (Object)null || (Object)(object)gameObject.GetComponent() != (Object)null || (Object)(object)gameObject.GetComponent() != (Object)null || (Object)(object)gameObject.GetComponent() != (Object)null || (Object)(object)gameObject.GetComponent() != (Object)null || (Object)(object)gameObject.GetComponent("SpectateCameraCollisionIgnore") != (Object)null) { return true; } val = val.parent; } return false; } private static void ApplyLiveCameraTransform(Camera liveCamera, Vector3 position, Quaternion rotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!LiveSpectateMath.IsFinite(position) || !LiveSpectateMath.IsFinite(rotation)) { GetFallbackCameraTransform(liveCamera, out position, out rotation); } if ((Object)(object)liveCamera != (Object)null) { ((Component)liveCamera).transform.SetPositionAndRotation(position, rotation); } } private static void GetFallbackCameraTransform(Camera liveCamera, out Vector3 position, out Quaternion rotation) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)liveCamera != (Object)null && LiveSpectateMath.IsFinite(((Component)liveCamera).transform.position) && LiveSpectateMath.IsFinite(((Component)liveCamera).transform.rotation)) { position = ((Component)liveCamera).transform.position; rotation = ((Component)liveCamera).transform.rotation; return; } GameDirector instance = GameDirector.instance; object obj; if (instance == null) { obj = null; } else { Camera mainCamera = instance.MainCamera; obj = ((mainCamera != null) ? ((Component)mainCamera).transform : null); } Transform val = (Transform)obj; if ((Object)(object)val != (Object)null && LiveSpectateMath.IsFinite(val.position) && LiveSpectateMath.IsFinite(val.rotation)) { position = val.position; rotation = val.rotation; } else { position = Vector3.zero; rotation = Quaternion.identity; } } private static bool IsPlayerInVehicle(PlayerAvatar player) { try { ItemVehicle vehicleForPlayer = ItemVehicle.GetVehicleForPlayer(player); return (Object)(object)vehicleForPlayer != (Object)null && (Object)(object)GetVehicleMeshTransform(vehicleForPlayer) != (Object)null; } catch { return false; } } private static Transform GetVehicleMeshTransform(ItemVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return null; } object memberValue = LiveSpectateReflection.GetMemberValue(vehicle, "meshTransform"); return (Transform)(((memberValue is Transform) ? memberValue : null) ?? ((Component)vehicle).transform); } private static float NormalizePitch(float value) { return (value > 180f) ? (value - 360f) : value; } } public sealed class LiveSpectateController : MonoBehaviour { private const float ToggleDebounceSeconds = 0.25f; private const float InputDisableSeconds = 0.25f; private readonly LiveSpectateResourceRestorer resourceRestorer = new LiveSpectateResourceRestorer(); private readonly LiveSpectateCameraFollower cameraFollower = new LiveSpectateCameraFollower(); private float nextToggleTime; private bool liveSpectateActive; private PlayerAvatar owner; private PlayerAvatar targetPlayer; private GameObject liveCameraObject; private Camera liveCamera; internal static LiveSpectateController Instance { get; set; } private void Update() { if (liveSpectateActive) { FreezeLocalControls(); HandleSpectateSwitchInput(); LiveSpectateHudService.UpdateLiveSpectateHud(targetPlayer); if (!LiveSpectateInputPolicy.CanContinueLiveSpectate(owner, out var reason)) { LiveSpectateLog.LogLifecycle("Continuation guard stopping live spectate. reason=" + reason + ".", "Continuation guard stopping local live spectate. reason=" + reason + "."); StopLiveSpectate(); } } Keyboard current = Keyboard.current; if (((current != null) ? current.kKey : null) == null || !((ButtonControl)Keyboard.current.kKey).wasPressedThisFrame) { return; } if (liveSpectateActive) { string allowDenyReason = "Allowed: active live spectate manual stop"; if (Time.unscaledTime < nextToggleTime) { allowDenyReason = $"Denied: debounce active until {nextToggleTime:0.000} (now {Time.unscaledTime:0.000})"; LogKPressDiagnostic(allowDenyReason); } else { LogKPressDiagnostic(allowDenyReason); nextToggleTime = Time.unscaledTime + 0.25f; StopLiveSpectate(); } return; } string reason2; bool flag = LiveSpectateInputPolicy.CanToggleNow(out reason2); if (flag && Time.unscaledTime < nextToggleTime) { flag = false; reason2 = $"Denied: debounce active until {nextToggleTime:0.000} (now {Time.unscaledTime:0.000})"; } if (flag) { flag = LiveSpectateInputPolicy.CanStart(PlayerAvatar.instance, out reason2); } LogKPressDiagnostic(reason2); if (flag) { nextToggleTime = Time.unscaledTime + 0.25f; TryStartLiveSpectate(); } } private void LateUpdate() { if (liveSpectateActive && !((Object)(object)liveCamera == (Object)null) && EnsureValidActiveTarget()) { cameraFollower.Update(liveCamera, targetPlayer); } } internal void HandleLocalPlayerDeath(PlayerAvatar player) { LiveSpectateLog.LogLifecycle($"Local player death observed. active={liveSpectateActive}, isOwner={(Object)(object)owner == (Object)(object)player}.", $"HandleLocalPlayerDeath. playerExists={(Object)(object)player != (Object)null}, liveSpectateActive={liveSpectateActive}, isOwner={(Object)(object)owner == (Object)(object)player}."); if (liveSpectateActive && (Object)(object)owner == (Object)(object)player) { StopLiveSpectate(); } } internal bool IsLiveSpectateOwner(PlayerAvatar player) { return liveSpectateActive && (Object)(object)owner == (Object)(object)player; } internal static bool IsLocalPlayer(PlayerAvatar player) { return LiveSpectatePlayerState.IsLocalPlayer(player); } internal void StopLiveSpectate() { LiveSpectateLog.LogLifecycle($"StopLiveSpectate begin. active={liveSpectateActive}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}.", $"StopLiveSpectate begin. active={liveSpectateActive}, ownerExists={(Object)(object)owner != (Object)null}, liveCameraExists={(Object)(object)liveCamera != (Object)null}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}."); bool flag = resourceRestorer.HasResources || (Object)(object)liveCameraObject != (Object)null || (Object)(object)liveCamera != (Object)null; if (!liveSpectateActive && !flag) { LiveSpectateLog.LogLifecycle("StopLiveSpectate skipped; inactive and no resources remain.", "StopLiveSpectate skipped because local live spectate is not active and no local resources remain."); return; } resourceRestorer.RestoreLightCullTarget(owner); LiveSpectateHudService.RestoreLiveSpectateHud(); liveSpectateActive = false; owner = null; targetPlayer = null; cameraFollower.Reset(); try { resourceRestorer.RestoreAudioListenerTarget(liveCamera); } catch (Exception arg) { LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate audio restore failed: {arg}"); } try { resourceRestorer.RestoreOriginalCameras(); resourceRestorer.RestoreCameraTags(); } catch (Exception arg2) { LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate camera restore failed: {arg2}"); } resourceRestorer.RestoreCameraUtilsMainCamera(liveCamera); try { if ((Object)(object)liveCameraObject != (Object)null) { Object.Destroy((Object)(object)liveCameraObject); } } catch (Exception arg3) { LiveSpectatePlugin.Log.LogWarning((object)$"StopLiveSpectate local camera destroy failed: {arg3}"); } finally { liveCameraObject = null; liveCamera = null; } LiveSpectateLog.LogLifecycle("StopLiveSpectate success. Local camera removed; originals restored.", "StopLiveSpectate success. Local-only camera destroyed; original cameras/audio targets restored. No player, CameraAim, or CameraPosition transforms were moved."); } private void TryStartLiveSpectate() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) LiveSpectateLog.LogLifecycle("StartLiveSpectate begin.", "StartLiveSpectate begin (local-only renderer architecture). No SpectateCamera/SetSpectate/CameraPosition bridge will be used."); PlayerAvatar instance = PlayerAvatar.instance; if (!LiveSpectateInputPolicy.CanStart(instance, out var reason)) { LiveSpectatePlugin.Log.LogWarning((object)("StartLiveSpectate denied. " + reason)); return; } PlayerAvatar firstSpectateTarget = LiveSpectateTargets.GetFirstSpectateTarget(instance); if ((Object)(object)firstSpectateTarget == (Object)null) { LiveSpectatePlugin.Log.LogWarning((object)"StartLiveSpectate denied. Denied: no enabled non-local player target exists"); return; } try { FreezeLocalControls(); CreateLiveCamera(); owner = instance; targetPlayer = firstSpectateTarget; resourceRestorer.CaptureOriginalLightCullTarget(); resourceRestorer.SetLightCullTarget(firstSpectateTarget); Vector3 targetFocusPoint = LiveSpectateCameraFollower.GetTargetFocusPoint(firstSpectateTarget); Quaternion basis = (((Object)(object)GameDirector.instance.MainCamera != (Object)null) ? ((Component)GameDirector.instance.MainCamera).transform.rotation : Quaternion.LookRotation(((Component)instance).transform.forward, Vector3.up)); cameraFollower.Initialize(liveCamera, basis, targetFocusPoint); resourceRestorer.MakeLiveCameraMainCamera(liveCamera); resourceRestorer.DisableOriginalCameras(GameDirector.instance.MainCamera); resourceRestorer.RedirectAudioListenerTarget(liveCamera); liveSpectateActive = true; bool flag = (Object)(object)((Component)liveCamera).GetComponent("PhotonView") != (Object)null; LiveSpectateLog.LogLifecycle("StartLiveSpectate success. target=" + LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget) + ".", $"StartLiveSpectate success. owner={LiveSpectateUtility.GetPlayerDisplayName(instance)}, target={LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget)}, liveCamera={((Object)liveCamera).name}, hasPhotonView={flag}, liveCameraTag={((Component)liveCamera).tag}."); } catch (Exception arg) { LiveSpectatePlugin.Log.LogWarning((object)$"Start local live spectate failed: {arg}"); liveSpectateActive = true; StopLiveSpectate(); } } private void CreateLiveCamera() { Camera mainCamera = GameDirector.instance.MainCamera; if ((Object)(object)mainCamera == (Object)null) { throw new InvalidOperationException("GameDirector.instance.MainCamera is null"); } float liveFieldOfView = (((Object)(object)CameraZoom.Instance != (Object)null) ? CameraZoom.Instance.playerZoomDefault : mainCamera.fieldOfView); LiveSpectateCameraCloneResult liveSpectateCameraCloneResult = LiveSpectateCameraFactory.Create(mainCamera, liveFieldOfView); liveCameraObject = liveSpectateCameraCloneResult.RootObject; liveCamera = liveSpectateCameraCloneResult.RootCamera; } private void FreezeLocalControls() { PlayerController instance = PlayerController.instance; if ((Object)(object)instance != (Object)null) { instance.InputDisable(0.25f); } } private void HandleSpectateSwitchInput() { if (SemiFunc.InputDown((InputKey)23)) { SwitchTarget(next: true); } else if (SemiFunc.InputDown((InputKey)24)) { SwitchTarget(next: false); } } private void SwitchTarget(bool next) { List spectateTargets = LiveSpectateTargets.GetSpectateTargets(owner); if (spectateTargets.Count == 0) { LiveSpectatePlugin.Log.LogInfo((object)"Target switch found no enabled non-local players; stopping local live spectate."); StopLiveSpectate(); return; } int num = spectateTargets.IndexOf(targetPlayer); int num2 = ((num >= 0) ? ((num + (next ? 1 : (-1)) + spectateTargets.Count) % spectateTargets.Count) : 0); targetPlayer = spectateTargets[num2]; cameraFollower.ResetForTarget(targetPlayer, liveCamera, applyTransform: false); resourceRestorer.SetLightCullTarget(targetPlayer); LiveSpectateLog.LogLifecycle("Target switched to " + LiveSpectateUtility.GetPlayerDisplayName(targetPlayer) + ".", $"LiveSpectate target switched. next={next}, index={num2}/{spectateTargets.Count}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}."); } private bool EnsureValidActiveTarget() { if (LiveSpectateTargets.IsValidSpectateTarget(targetPlayer, owner)) { return true; } PlayerAvatar player = targetPlayer; PlayerAvatar firstSpectateTarget = LiveSpectateTargets.GetFirstSpectateTarget(owner); if ((Object)(object)firstSpectateTarget == (Object)null) { LiveSpectatePlugin.Log.LogInfo((object)("Active target revalidation found no valid replacement after target became invalid (" + LiveSpectateUtility.GetPlayerDisplayName(player) + "); stopping local live spectate.")); StopLiveSpectate(); return false; } targetPlayer = firstSpectateTarget; cameraFollower.ResetForTarget(targetPlayer, liveCamera, applyTransform: false); resourceRestorer.SetLightCullTarget(targetPlayer); LiveSpectatePlugin.Log.LogInfo((object)("Active target revalidation retargeted local live spectate from " + LiveSpectateUtility.GetPlayerDisplayName(player) + " to " + LiveSpectateUtility.GetPlayerDisplayName(firstSpectateTarget) + ".")); return true; } private void LogKPressDiagnostic(string allowDenyReason) { try { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics == null || !verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogInfo((object)$"LiveSpectate K pressed. active={liveSpectateActive}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}, decision={allowDenyReason}."); return; } PlayerAvatar instance = PlayerAvatar.instance; object memberValue = LiveSpectateReflection.GetMemberValue(MenuManager.instance, "currentMenuPage"); object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("PlayerController", "instance"); object obj = LiveSpectateReflection.GetMemberValue(staticMemberValue, "playerAvatarScript") ?? LiveSpectateReflection.GetMemberValue(staticMemberValue, "playerAvatar"); LiveSpectateTargets.GetPlayerListCounts(instance, out var playerCount, out var enabledPlayerCount, out var enabledNonLocalCount); LiveSpectateInputPolicy.HasPlayableScene(out var reason); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("LiveSpectate K press diagnostic:"); stringBuilder.AppendLine($" time={Time.unscaledTime:0.000}, frame={Time.frameCount}"); stringBuilder.AppendLine($" liveSpectateActive={liveSpectateActive}, liveCamera.exists={(Object)(object)liveCamera != (Object)null}, liveCameraObject.exists={(Object)(object)liveCameraObject != (Object)null}, target={LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)}"); object[] obj2 = new object[4] { (Object)(object)GameDirector.instance != (Object)null, null, null, null }; GameDirector instance2 = GameDirector.instance; obj2[1] = ((instance2 != null) ? ((object)(gameState)(ref instance2.currentState)).ToString() : null) ?? ""; obj2[2] = (((Object)(object)GameDirector.instance != (Object)null) ? GameDirector.instance.DisableInput.ToString() : ""); obj2[3] = (Object)(object)GameDirector.instance?.MainCamera != (Object)null; stringBuilder.AppendLine(string.Format(" GameDirector.exists={0}, currentState={1}, DisableInput={2}, MainCamera.exists={3}", obj2)); stringBuilder.AppendLine(string.Format(" LevelGenerator.exists={0}, Generated={1}", (Object)(object)LevelGenerator.Instance != (Object)null, ((Object)(object)LevelGenerator.Instance != (Object)null) ? LevelGenerator.Instance.Generated.ToString() : "")); stringBuilder.AppendLine(string.Format(" sceneChecks: MenuLevel={0}, RunIsLobbyMenu={1}, RunIsLobby={2}, RunIsShop={3}, RunIsLevel={4}, RunIsArena={5}, RunIsTutorial={6}, playableReason={7}", LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.MenuLevel, "MenuLevel"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsLobbyMenu, "RunIsLobbyMenu"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsLobby, "RunIsLobby"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsShop, "RunIsShop"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsLevel, "RunIsLevel"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsArena, "RunIsArena"), LiveSpectateInputPolicy.SafeSceneBool((Func)SemiFunc.RunIsTutorial, "RunIsTutorial"), reason)); stringBuilder.AppendLine(string.Format(" ChatManager.exists={0}, chatActive={1}", (Object)(object)ChatManager.instance != (Object)null, LiveSpectateReflection.GetBoolMember(ChatManager.instance, "chatActive"))); stringBuilder.AppendLine($" MenuManager.exists={(Object)(object)MenuManager.instance != (Object)null}, currentMenuPage={LiveSpectateUtility.DescribeObject(memberValue)}"); stringBuilder.AppendLine($" Keyboard.current.exists={Keyboard.current != null}"); stringBuilder.AppendLine(string.Format(" PlayerAvatar.instance.exists={0}, name={1}, isLocal={2}, spectating={3}, isDisabled={4}, deadSet={5}, activeSelf={6}, spectatePointNull={7}", (Object)(object)instance != (Object)null, LiveSpectateUtility.GetPlayerDisplayName(instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.IsLocalField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.SpectatingField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.IsDisabledField, instance), LiveSpectatePlayerState.GetBool(LiveSpectatePlayerState.DeadSetField, instance), ((Object)(object)instance != (Object)null) ? ((Component)instance).gameObject.activeSelf.ToString() : "", (Object)(object)instance?.spectatePoint == (Object)null)); stringBuilder.AppendLine($" PlayerController.instance.exists={staticMemberValue != null}, playerAvatarScript.exists={obj != null}, equalsPlayerAvatar.instance={obj == instance}"); stringBuilder.AppendLine(" Vanilla SpectateCamera is intentionally not inspected or used by LiveSpectate."); stringBuilder.AppendLine($" CameraAim.Instance.exists={(Object)(object)CameraAim.Instance != (Object)null}, CameraPosition.instance.exists={(Object)(object)CameraPosition.instance != (Object)null} (diagnostic only; LiveSpectate does not move/use bridge)"); stringBuilder.AppendLine($" PlayerList.count={playerCount}, enabledPlayerCount={enabledPlayerCount}, enabledNonLocalTargetCount={enabledNonLocalCount}"); stringBuilder.AppendLine(" decision=" + allowDenyReason); LiveSpectatePlugin.Log.LogInfo((object)stringBuilder.ToString().TrimEnd()); } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate K press diagnostic failed: " + ex.GetType().Name + ": " + ex.Message)); } } } internal static class LiveSpectateHudService { internal static void UpdateLiveSpectateHud(PlayerAvatar targetPlayer) { try { SemiFunc.UIHideHealth(); SemiFunc.UIHideOvercharge(); SemiFunc.UIHideEnergy(); SemiFunc.UIHideInventory(); SemiFunc.UIHideAim(); MissionUI instance = MissionUI.instance; if (instance != null) { ((SemiUI)instance).Hide(); } if ((Object)(object)targetPlayer != (Object)null) { SemiFunc.HUDSpectateSetName(LiveSpectateUtility.GetPlayerDisplayName(targetPlayer)); SpectateNameUI instance2 = SpectateNameUI.instance; if (instance2 != null) { ((SemiUI)instance2).Show(); } } } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate HUD update failed: " + ex.GetType().Name + ": " + ex.Message)); } } } internal static void RestoreLiveSpectateHud() { try { SpectateNameUI instance = SpectateNameUI.instance; if (instance != null) { ((SemiUI)instance).Hide(); } } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate HUD restore failed: " + ex.GetType().Name + ": " + ex.Message)); } } } } internal static class LiveSpectateInputPolicy { internal static bool CanStart(PlayerAvatar player, out string reason) { if ((Object)(object)player == (Object)null) { reason = "Denied: PlayerAvatar.instance is null"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || !value) { reason = "Denied: PlayerAvatar.instance is not local or isLocal is unreadable"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2) { reason = "Denied: local player is disabled or isDisabled is unreadable"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3) { reason = "Denied: local player deadSet is true or unreadable"; return false; } if (!CanToggleNow(out reason)) { return false; } if ((Object)(object)LiveSpectateTargets.GetFirstSpectateTarget(player) == (Object)null) { reason = "Denied: no enabled non-local player target exists"; return false; } reason = "Allowed: playable-scene, input, local-player, and non-local target checks passed"; return true; } internal static bool CanContinueLiveSpectate(PlayerAvatar player, out string reason) { if ((Object)(object)player == (Object)null) { reason = "owner is null"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || !value) { reason = "owner is not local or isLocal is unreadable"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2) { reason = "owner is disabled or isDisabled is unreadable"; return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3) { reason = "owner deadSet is true or unreadable"; return false; } if (!HasPlayableScene(out reason)) { return false; } if ((Object)(object)LiveSpectateTargets.GetFirstSpectateTarget(player) == (Object)null) { reason = "no enabled non-local player target exists"; return false; } reason = "continuation allowed"; return true; } internal static bool CanToggleNow(out string reason) { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null) { reason = "Denied: GameDirector.instance is null"; return false; } if (instance.DisableInput) { reason = "Denied: GameDirector.DisableInput is true"; return false; } if (LiveSpectateReflection.GetBoolMember(ChatManager.instance, "chatActive")) { reason = "Denied: ChatManager.chatActive is true"; return false; } if (!HasPlayableScene(out reason)) { reason = "Denied: " + reason; return false; } reason = "Allowed: playable scene and no blocking chat/input state"; return true; } internal static bool HasPlayableScene(out string reason) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_003e: Unknown result type (might be due to invalid IL or missing references) GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null) { reason = "GameDirector.instance is null"; return false; } if ((int)instance.currentState != 2) { reason = $"GameDirector.currentState is {instance.currentState}, expected Main"; return false; } if ((Object)(object)LevelGenerator.Instance == (Object)null) { reason = "LevelGenerator.Instance is null"; return false; } if (!LevelGenerator.Instance.Generated) { reason = "LevelGenerator.Instance.Generated is false"; return false; } bool flag = SafeSceneBool((Func)SemiFunc.MenuLevel, "MenuLevel"); bool flag2 = SafeSceneBool((Func)SemiFunc.RunIsLobbyMenu, "RunIsLobbyMenu"); bool flag3 = SafeSceneBool((Func)SemiFunc.RunIsLobby, "RunIsLobby"); bool flag4 = SafeSceneBool((Func)SemiFunc.RunIsShop, "RunIsShop"); if (flag || flag2 || flag3 || flag4) { reason = $"blocked scene state: MenuLevel={flag}, RunIsLobbyMenu={flag2}, RunIsLobby={flag3}, RunIsShop={flag4}"; return false; } bool flag5 = SafeSceneBool((Func)SemiFunc.RunIsLevel, "RunIsLevel"); bool flag6 = SafeSceneBool((Func)SemiFunc.RunIsArena, "RunIsArena"); bool flag7 = SafeSceneBool((Func)SemiFunc.RunIsTutorial, "RunIsTutorial"); if (!flag5 && !flag6 && !flag7) { reason = $"not a playable scene: RunIsLevel={flag5}, RunIsArena={flag6}, RunIsTutorial={flag7}"; return false; } reason = $"playable scene confirmed: Generated=true, RunIsLevel={flag5}, RunIsArena={flag6}, RunIsTutorial={flag7}"; return true; } internal static bool SafeSceneBool(Func getter, string name) { try { return getter(); } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("Scene check " + name + " failed: " + ex.GetType().Name + ": " + ex.Message)); } return false; } } } internal static class LiveSpectateLog { internal static void LogLifecycle(string compactMessage, string verboseMessage) { ManualLogSource log = LiveSpectatePlugin.Log; ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; log.LogInfo((object)((verboseDiagnostics != null && verboseDiagnostics.Value) ? verboseMessage : compactMessage)); } } internal static class LiveSpectateMath { internal static bool IsFinite(Vector3 value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z); } internal static bool IsFinite(Quaternion value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) return IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z) && IsFinite(value.w); } internal static bool IsFinite(float value) { return !float.IsNaN(value) && !float.IsInfinity(value); } } internal static class LiveSpectatePlayerState { internal static readonly FieldInfo IsLocalField = GetPlayerField("isLocal"); internal static readonly FieldInfo IsDisabledField = GetPlayerField("isDisabled"); internal static readonly FieldInfo SpectatingField = GetPlayerField("spectating"); internal static readonly FieldInfo DeadSetField = GetPlayerField("deadSet"); internal static bool IsLocalPlayer(PlayerAvatar player) { bool value = default(bool); return (Object)(object)player != (Object)null && TryGetBool(IsLocalField, player, out value) && value; } internal static bool GetBool(FieldInfo field, PlayerAvatar player) { bool value; return TryGetBool(field, player, out value) && value; } internal static bool TryGetBool(FieldInfo field, PlayerAvatar player, out bool value) { value = false; if (field == null || (Object)(object)player == (Object)null) { return false; } try { object value2 = field.GetValue(player); if (value2 is bool) { bool flag = (bool)value2; if (true) { value = flag; return true; } } } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("PlayerAvatar bool field read failed for " + field.Name + ": " + ex.GetType().Name + ": " + ex.Message)); } } return false; } private static FieldInfo GetPlayerField(string fieldName) { FieldInfo field = typeof(PlayerAvatar).GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { ManualLogSource log = LiveSpectatePlugin.Log; if (log != null) { log.LogWarning((object)("PlayerAvatar." + fieldName + " field was not found.")); } } return field; } } [BepInPlugin("com.zhuanban.repo.livespectate", "LiveSpectate", "1.0.0")] public sealed class LiveSpectatePlugin : BaseUnityPlugin { public const string PluginGuid = "com.zhuanban.repo.livespectate"; public const string PluginName = "LiveSpectate"; public const string PluginVersion = "1.0.0"; private Harmony harmony; internal static ManualLogSource Log { get; private set; } internal static ConfigEntry VerboseDiagnostics { get; private set; } private void Awake() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; VerboseDiagnostics = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "VerboseDiagnostics", false, "Enable extra LiveSpectate diagnostic logging. Default logging stays compact."); Log.LogInfo((object)string.Format("{0} awake. Version={1}, verboseDiagnostics={2}.", "LiveSpectate", "1.0.0", VerboseDiagnostics.Value)); LiveSpectateController instance = ((Component)this).gameObject.AddComponent(); LiveSpectateController.Instance = instance; Log.LogInfo((object)"LiveSpectateController attached."); harmony = new Harmony("com.zhuanban.repo.livespectate"); harmony.PatchAll(); Log.LogInfo((object)"Harmony patches applied."); Log.LogInfo((object)"LiveSpectate 1.0.0 loaded."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if ((Object)(object)LiveSpectateController.Instance != (Object)null) { LiveSpectateController.Instance.StopLiveSpectate(); LiveSpectateController.Instance = null; } } } internal static class LiveSpectateReflection { internal static bool GetBoolMember(object target, string memberName) { object memberValue = GetMemberValue(target, memberName); bool flag = default(bool); int num; if (memberValue is bool) { flag = (bool)memberValue; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static float GetFloatMember(object target, string memberName, float fallback) { object memberValue = GetMemberValue(target, memberName); if (memberValue is float result) { return result; } if (memberValue is int num) { return num; } return fallback; } internal static object GetMemberValue(object target, string memberName) { if (target == null) { return null; } try { Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(field.IsStatic ? null : target); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue((property.GetMethod != null && property.GetMethod.IsStatic) ? null : target); } type = type.BaseType; } return null; } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("Reflection read failed for " + memberName + ": " + ex.GetType().Name)); } return null; } } internal static bool TrySetMemberValue(object target, string memberName, object value) { if (target == null) { return false; } try { Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(field.IsStatic ? null : target, value); return true; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && property.SetMethod != null) { property.SetValue(property.SetMethod.IsStatic ? null : target, value); return true; } type = type.BaseType; } return false; } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("Reflection write failed for " + memberName + ": " + ex.GetType().Name)); } return false; } } internal static object GetStaticMemberValue(string typeName, string memberName) { try { Type type = FindType(typeName); if (type == null) { return null; } FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(null); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || property.GetIndexParameters().Length != 0) { return null; } return property.GetValue(null); } catch (Exception ex) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { LiveSpectatePlugin.Log.LogWarning((object)("Static reflection read failed for " + typeName + "." + memberName + ": " + ex.GetType().Name)); } return null; } } internal static Type FindType(string typeName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType(typeName); if (type != null) { return type; } } return null; } } internal sealed class LiveSpectateResourceRestorer { private readonly struct CameraState { internal Camera Camera { get; } internal bool OriginalEnabled { get; } internal bool AssignedEnabled { get; } internal CameraState(Camera camera, bool originalEnabled, bool assignedEnabled) { Camera = camera; OriginalEnabled = originalEnabled; AssignedEnabled = assignedEnabled; } } private readonly struct CameraTagState { internal Camera Camera { get; } internal string OriginalTag { get; } internal string AssignedTag { get; } internal CameraTagState(Camera camera, string originalTag, string assignedTag) { Camera = camera; OriginalTag = originalTag; AssignedTag = assignedTag; } } private const string MainCameraTag = "MainCamera"; private CameraState[] disabledCameraStates; private CameraTagState[] originalCameraTagStates; private Camera originalCameraUtilsMainCamera; private bool originalCameraUtilsMainCameraCaptured; private Transform originalAudioTargetPosition; private Transform originalAudioTargetRotation; private Transform originalLightCullTarget; private bool originalLightCullTargetCaptured; private Transform liveSpectateLightCullTarget; internal bool HasResources => disabledCameraStates != null || originalCameraTagStates != null || originalCameraUtilsMainCameraCaptured || (Object)(object)originalAudioTargetPosition != (Object)null || (Object)(object)originalAudioTargetRotation != (Object)null || originalLightCullTargetCaptured || (Object)(object)liveSpectateLightCullTarget != (Object)null; internal void DisableOriginalCameras(Camera mainCamera) { Camera[] componentsInChildren = ((Component)mainCamera).GetComponentsInChildren(true); disabledCameraStates = new CameraState[componentsInChildren.Length]; for (int i = 0; i < componentsInChildren.Length; i++) { bool enabled = ((Behaviour)componentsInChildren[i]).enabled; disabledCameraStates[i] = new CameraState(componentsInChildren[i], enabled, assignedEnabled: false); ((Behaviour)componentsInChildren[i]).enabled = false; } LiveSpectateLog.LogLifecycle($"Disabled {componentsInChildren.Length} original camera(s).", $"Disabled {componentsInChildren.Length} original main-camera hierarchy cameras for local live spectate; stored enabled states for restore."); } internal void MakeLiveCameraMainCamera(Camera liveCamera) { if ((Object)(object)liveCamera == (Object)null) { return; } try { List list = new List(); originalCameraTagStates = list.ToArray(); Camera[] allCameras = Camera.allCameras; Camera[] array = allCameras; foreach (Camera val in array) { if (!((Object)(object)val == (Object)null)) { string text = LiveSpectateUtility.SafeGetTag(((Component)val).gameObject); bool flag = (Object)(object)val == (Object)(object)liveCamera; if (flag ? (text != "MainCamera") : (text == "MainCamera")) { string text2 = (flag ? "MainCamera" : "Untagged"); list.Add(new CameraTagState(val, text, text2)); originalCameraTagStates = list.ToArray(); ((Component)val).gameObject.tag = text2; } } } if (LiveSpectateUtility.SafeGetTag(((Component)liveCamera).gameObject) != "MainCamera") { list.Add(new CameraTagState(liveCamera, LiveSpectateUtility.SafeGetTag(((Component)liveCamera).gameObject), "MainCamera")); originalCameraTagStates = list.ToArray(); ((Component)liveCamera).gameObject.tag = "MainCamera"; } originalCameraTagStates = list.ToArray(); LiveSpectateLog.LogLifecycle("Live camera tagged MainCamera for compatibility.", $"Live camera tagged MainCamera for compatibility. changedTags={originalCameraTagStates.Length}."); } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate MainCamera tag handoff failed: " + ex.GetType().Name + ": " + ex.Message)); } CaptureAndSetCameraUtilsMainCamera(liveCamera); } internal void RestoreOriginalCameras() { if (disabledCameraStates == null) { return; } int num = 0; int num2 = 0; int num3 = 0; CameraState[] array = disabledCameraStates; for (int i = 0; i < array.Length; i++) { CameraState cameraState = array[i]; try { if ((Object)(object)cameraState.Camera != (Object)null) { if (((Behaviour)cameraState.Camera).enabled != cameraState.AssignedEnabled) { num3++; continue; } ((Behaviour)cameraState.Camera).enabled = cameraState.OriginalEnabled; num++; } } catch (Exception ex) { num2++; LiveSpectatePlugin.Log.LogWarning((object)("Failed to restore original camera enabled state: " + ex.GetType().Name + ": " + ex.Message)); } } LiveSpectateLog.LogLifecycle($"Restored {num}/{disabledCameraStates.Length} original camera enabled state(s). skipped={num3}.", $"Restored {num}/{disabledCameraStates.Length} original camera enabled states. failed={num2}, skippedOwnershipChanged={num3}."); disabledCameraStates = null; } internal void RestoreCameraTags() { if (originalCameraTagStates == null) { return; } int num = 0; int num2 = 0; int num3 = 0; CameraTagState[] array = originalCameraTagStates; for (int i = 0; i < array.Length; i++) { CameraTagState cameraTagState = array[i]; try { if ((Object)(object)cameraTagState.Camera != (Object)null) { string text = LiveSpectateUtility.SafeGetTag(((Component)cameraTagState.Camera).gameObject); if (text != cameraTagState.AssignedTag) { num3++; continue; } ((Component)cameraTagState.Camera).gameObject.tag = cameraTagState.OriginalTag; num++; } } catch (Exception ex) { num2++; LiveSpectatePlugin.Log.LogWarning((object)("Failed to restore camera tag: " + ex.GetType().Name + ": " + ex.Message)); } } LiveSpectateLog.LogLifecycle($"Restored {num}/{originalCameraTagStates.Length} camera tag(s). skipped={num3}.", $"Restored {num}/{originalCameraTagStates.Length} camera tags. failed={num2}, skippedOwnershipChanged={num3}."); originalCameraTagStates = null; } internal void RestoreCameraUtilsMainCamera(Camera liveCamera) { if (!originalCameraUtilsMainCameraCaptured) { return; } try { object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("CameraUtils", "Instance"); Camera val = (Camera)((staticMemberValue != null) ? /*isinst with value type is only supported in some contexts*/: null); if (staticMemberValue != null && (Object)(object)val == (Object)(object)liveCamera && LiveSpectateReflection.TrySetMemberValue(staticMemberValue, "MainCamera", originalCameraUtilsMainCamera)) { LiveSpectateLog.LogLifecycle("CameraUtils MainCamera restored.", "CameraUtils.Instance.MainCamera restored to its captured original camera."); } else if (staticMemberValue != null) { LiveSpectateLog.LogLifecycle("CameraUtils MainCamera restore skipped; current camera is no longer live camera.", "CameraUtils.Instance.MainCamera restore skipped because current=" + LiveSpectateUtility.DescribeCamera(val) + " is not the LiveSpectate-owned live camera=" + LiveSpectateUtility.DescribeCamera(liveCamera) + "."); } } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate CameraUtils MainCamera restore failed: " + ex.GetType().Name + ": " + ex.Message)); } finally { originalCameraUtilsMainCamera = null; originalCameraUtilsMainCameraCaptured = false; } } internal void RedirectAudioListenerTarget(Camera liveCamera) { AudioListenerFollow instance = AudioListenerFollow.instance; if ((Object)(object)instance == (Object)null || (Object)(object)liveCamera == (Object)null) { LiveSpectatePlugin.Log.LogInfo((object)$"Audio listener target redirect skipped. AudioListenerFollow.exists={(Object)(object)instance != (Object)null}, liveCamera.exists={(Object)(object)liveCamera != (Object)null}."); return; } originalAudioTargetPosition = instance.TargetPositionTransform; originalAudioTargetRotation = instance.TargetRotationTransform; instance.TargetPositionTransform = ((Component)liveCamera).transform; instance.TargetRotationTransform = ((Component)liveCamera).transform; LiveSpectatePlugin.Log.LogInfo((object)"AudioListenerFollow target redirected to local live camera."); } internal void RestoreAudioListenerTarget(Camera liveCamera) { AudioListenerFollow instance = AudioListenerFollow.instance; if ((Object)(object)instance == (Object)null) { originalAudioTargetPosition = null; originalAudioTargetRotation = null; return; } Transform val = ((liveCamera != null) ? ((Component)liveCamera).transform : null); GameDirector instance2 = GameDirector.instance; object obj; if (instance2 == null) { obj = null; } else { Camera mainCamera = instance2.MainCamera; obj = ((mainCamera != null) ? ((Component)mainCamera).transform : null); } Transform val2 = (Transform)obj; int num = 0; int num2 = 0; if ((Object)(object)instance.TargetPositionTransform == (Object)null || (Object)(object)instance.TargetPositionTransform == (Object)(object)val) { instance.TargetPositionTransform = (((Object)(object)originalAudioTargetPosition != (Object)null) ? originalAudioTargetPosition : val2); num++; } else { num2++; } if ((Object)(object)instance.TargetRotationTransform == (Object)null || (Object)(object)instance.TargetRotationTransform == (Object)(object)val) { instance.TargetRotationTransform = (((Object)(object)originalAudioTargetRotation != (Object)null) ? originalAudioTargetRotation : val2); num++; } else { num2++; } originalAudioTargetPosition = null; originalAudioTargetRotation = null; LiveSpectatePlugin.Log.LogInfo((object)$"AudioListenerFollow target restore complete. restored={num}, skipped={num2}."); } internal void CaptureOriginalLightCullTarget() { if (originalLightCullTargetCaptured) { return; } try { originalLightCullTarget = GetCurrentLightCullTarget(); originalLightCullTargetCaptured = true; LiveSpectateLog.LogLifecycle("Captured original LightManager cull target.", "Captured original LightManager cull target: " + LiveSpectateUtility.DescribeTransform(originalLightCullTarget) + "."); } catch (Exception ex) { originalLightCullTarget = null; originalLightCullTargetCaptured = true; LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target capture failed: " + ex.GetType().Name + ": " + ex.Message)); } } internal void SetLightCullTarget(PlayerAvatar player) { Transform val = (((Object)(object)player != (Object)null) ? ((Component)player).transform : null); if ((Object)(object)val == (Object)null) { return; } try { SemiFunc.LightManagerSetCullTargetTransform(val); liveSpectateLightCullTarget = val; LightManager instance = LightManager.instance; if (instance != null) { instance.UpdateInstant(); } LiveSpectateLog.LogLifecycle("Light cull target set to " + LiveSpectateUtility.GetPlayerDisplayName(player) + ".", "LightManager cull target set to live spectate target " + LiveSpectateUtility.GetPlayerDisplayName(player) + " and updated instantly."); } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target set failed: " + ex.GetType().Name + ": " + ex.Message)); } } internal void RestoreLightCullTarget(PlayerAvatar owner) { bool flag = originalLightCullTargetCaptured && (Object)(object)originalLightCullTarget != (Object)null; object obj; if (!flag) { if (!((Object)(object)owner != (Object)null)) { PlayerAvatar instance = PlayerAvatar.instance; obj = ((instance != null) ? ((Component)instance).transform : null); } else { obj = ((Component)owner).transform; } } else { obj = originalLightCullTarget; } Transform val = (Transform)obj; if ((Object)(object)val == (Object)null) { originalLightCullTarget = null; originalLightCullTargetCaptured = false; liveSpectateLightCullTarget = null; return; } try { Transform currentLightCullTarget = GetCurrentLightCullTarget(); if ((Object)(object)currentLightCullTarget == (Object)null || (Object)(object)currentLightCullTarget == (Object)(object)liveSpectateLightCullTarget) { SemiFunc.LightManagerSetCullTargetTransform(val); LightManager instance2 = LightManager.instance; if (instance2 != null) { instance2.UpdateInstant(); } LiveSpectateLog.LogLifecycle("Light cull target restored.", "LightManager cull target restored to " + (flag ? LiveSpectateUtility.DescribeTransform(val) : ("fallback local/owner player " + LiveSpectateUtility.GetPlayerDisplayName(owner ?? PlayerAvatar.instance))) + " and updated instantly."); } else { LiveSpectateLog.LogLifecycle("Light cull target restore skipped; current target changed.", "LightManager cull target restore skipped because current=" + LiveSpectateUtility.DescribeTransform(currentLightCullTarget) + " is not LiveSpectate-owned target=" + LiveSpectateUtility.DescribeTransform(liveSpectateLightCullTarget) + "."); } } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate light cull target restore failed: " + ex.GetType().Name + ": " + ex.Message)); } finally { originalLightCullTarget = null; originalLightCullTargetCaptured = false; liveSpectateLightCullTarget = null; } } private void CaptureAndSetCameraUtilsMainCamera(Camera liveCamera) { if ((Object)(object)liveCamera == (Object)null) { return; } try { object staticMemberValue = LiveSpectateReflection.GetStaticMemberValue("CameraUtils", "Instance"); if (staticMemberValue != null) { if (!originalCameraUtilsMainCameraCaptured) { ref Camera reference = ref originalCameraUtilsMainCamera; object memberValue = LiveSpectateReflection.GetMemberValue(staticMemberValue, "MainCamera"); reference = (Camera)((memberValue is Camera) ? memberValue : null); originalCameraUtilsMainCameraCaptured = true; } if (LiveSpectateReflection.TrySetMemberValue(staticMemberValue, "MainCamera", liveCamera)) { LiveSpectateLog.LogLifecycle("CameraUtils MainCamera redirected to live camera.", "CameraUtils.Instance.MainCamera redirected to the local live spectate camera for vanilla utility visual parity."); } } } catch (Exception ex) { LiveSpectatePlugin.Log.LogWarning((object)("LiveSpectate CameraUtils MainCamera redirect failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static Transform GetCurrentLightCullTarget() { LightManager instance = LightManager.instance; if ((Object)(object)instance == (Object)null) { return null; } object memberValue = LiveSpectateReflection.GetMemberValue(instance, "lightCullTarget"); return (Transform)((memberValue is Transform) ? memberValue : null); } } internal static class LiveSpectateTargets { internal static PlayerAvatar GetFirstSpectateTarget(PlayerAvatar localOwner) { List spectateTargets = GetSpectateTargets(localOwner); return (spectateTargets.Count > 0) ? spectateTargets[0] : null; } internal static List GetSpectateTargets(PlayerAvatar localOwner) { List list = new List(); GameDirector instance = GameDirector.instance; if (instance?.PlayerList == null) { return list; } foreach (PlayerAvatar player in instance.PlayerList) { if (IsValidSpectateTarget(player, localOwner)) { list.Add(player); } } return list; } internal static bool IsValidSpectateTarget(PlayerAvatar player, PlayerAvatar localOwner) { if ((Object)(object)player == (Object)null || (Object)(object)player == (Object)(object)localOwner || !((Component)player).gameObject.activeInHierarchy) { return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value) || value) { return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value2) || value2) { return false; } if (!LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value3) || value3) { return false; } return true; } internal static void GetPlayerListCounts(PlayerAvatar localOwner, out int playerCount, out int enabledPlayerCount, out int enabledNonLocalCount) { playerCount = -1; enabledPlayerCount = -1; enabledNonLocalCount = -1; GameDirector instance = GameDirector.instance; if (instance?.PlayerList == null) { return; } playerCount = 0; enabledPlayerCount = 0; enabledNonLocalCount = 0; foreach (PlayerAvatar player in instance.PlayerList) { playerCount++; if ((Object)(object)player != (Object)null && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsDisabledField, player, out var value) && !value && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.DeadSetField, player, out var value2) && !value2) { enabledPlayerCount++; if ((Object)(object)player != (Object)(object)localOwner && LiveSpectatePlayerState.TryGetBool(LiveSpectatePlayerState.IsLocalField, player, out var value3) && !value3) { enabledNonLocalCount++; } } } } } internal static class LiveSpectateUtility { internal const string UntaggedTag = "Untagged"; internal static string GetPlayerDisplayName(PlayerAvatar player) { if ((Object)(object)player == (Object)null) { return ""; } return (LiveSpectateReflection.GetMemberValue(player, "playerName") ?? LiveSpectateReflection.GetMemberValue(player, "name"))?.ToString() ?? ((Object)player).name; } internal static string DescribeObject(object value) { if (value == null) { return ""; } string text; try { text = value.ToString(); } catch (Exception ex) { text = ""; } Object val = (Object)((value is Object) ? value : null); string text2 = ((val == null) ? (LiveSpectateReflection.GetMemberValue(value, "name") as string) : ((val != (Object)null) ? val.name : "")); string fullName = value.GetType().FullName; return "value=" + text + ", name=" + (text2 ?? "") + ", type=" + fullName; } internal static string DescribeCamera(Camera camera) { return ((Object)(object)camera != (Object)null) ? (((Object)camera).name + " (" + ((object)camera).GetType().FullName + ")") : ""; } internal static string DescribeTransform(Transform transform) { if ((Object)(object)transform == (Object)null) { return ""; } return ((Object)transform).name + " (" + ((object)transform).GetType().FullName + ")"; } internal static string SafeGetTag(GameObject gameObject) { try { return ((Object)(object)gameObject != (Object)null) ? gameObject.tag : "Untagged"; } catch { return "Untagged"; } } } } namespace LiveSpectate.Patches { [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] internal static class PlayerAvatarDeathPatch { private static void Prefix(PlayerAvatar __instance) { bool flag = LiveSpectateController.IsLocalPlayer(__instance); bool flag2 = flag && (Object)(object)LiveSpectateController.Instance != (Object)null && LiveSpectateController.Instance.IsLiveSpectateOwner(__instance); if (!flag) { ConfigEntry verboseDiagnostics = LiveSpectatePlugin.VerboseDiagnostics; if (verboseDiagnostics == null || !verboseDiagnostics.Value) { goto IL_0071; } } LiveSpectatePlugin.Log.LogInfo((object)$"PlayerDeathRPC prefix. playerExists={(Object)(object)__instance != (Object)null}, isLocal={flag}, willStopLiveSpectate={flag2}, skipOriginal=false."); goto IL_0071; IL_0071: if (flag) { LiveSpectateController.Instance?.HandleLocalPlayerDeath(__instance); } } } }