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