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 LethalCCTV v1.0.1
LethalCCTV.dll
Decompiled an hour ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using CSync.Extensions; using CSync.Lib; using DunGen; using DunGen.Graph; using DunGen.Tags; using GameNetcodeStuff; using HarmonyLib; using LethalCCTV.NetcodePatcher; using LethalCompanyInputUtils.Api; using LethalLib.Modules; using LethalNetworkAPI; using Microsoft.CodeAnalysis; using TMPro; using Unity.Collections; using Unity.Netcode; using Unity.Netcode.Components; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.Video; using Y4NGZCompany.Bootstrap; using Y4NGZCompany.Core; using Y4NGZCompany.Core.Compat; using Y4NGZCompany.Facility.Cameras; using Y4NGZCompany.Facility.Cameras.Placement; using Y4NGZCompany.Facility.Cameras.Probe; using Y4NGZCompany.Facility.Interior; using Y4NGZCompany.Facility.Interior.Placement; using Y4NGZCompany.Facility.Interior.Placement.Authored; using Y4NGZCompany.Facility.Mainframe; using Y4NGZCompany.Facility.Security; using Y4NGZCompany.Facility.Shared; using Y4NGZCompany.Facility.Stash; using Y4NGZCompany.ShipSystems.Rendering; using Y4NGZCompany.ShipSystems.Surveillance; using Y4NGZCompany.ShipSystems.Surveillance.OutlineEffect; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("ClientNetworkTransform")] [assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")] [assembly: IgnoresAccessChecksTo("DissonanceVoip")] [assembly: IgnoresAccessChecksTo("DunGen")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")] [assembly: IgnoresAccessChecksTo("EasyTextEffects")] [assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.Jobs")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")] [assembly: IgnoresAccessChecksTo("Unity.Services.QoS")] [assembly: IgnoresAccessChecksTo("Unity.Services.Relay")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")] [assembly: IgnoresAccessChecksTo("Unity.XR.Management")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalCCTV")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Y4NGZ surveillance and CCTV systems")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+6ae9e9f17528b47cbc796b51a05c6f7380dda81b")] [assembly: AssemblyProduct("LethalCCTV")] [assembly: AssemblyTitle("LethalCCTV")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] [module: NetcodePatchedAssembly] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalCCTV { [BepInPlugin("com.y4ngz.company.lethalcctv", "LethalCCTV", "1.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class LethalCCTVPlugin : BaseUnityPlugin { public const string PluginGuid = "com.y4ngz.company.lethalcctv"; public const string PluginName = "LethalCCTV"; public const string PluginVersion = "1.0.1"; private SurveillanceBootstrap _host; internal const string HostObjectName = "LethalCCTV_Host"; public static LethalCCTVPlugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //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_004a: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; DependencyVersionAudit.RunForLethalCCTV(((BaseUnityPlugin)this).Logger); NetcodeRuntimeBootstrap.Initialize(((BaseUnityPlugin)this).Logger, typeof(LethalCCTVPlugin).Assembly, true); GameObject val = new GameObject("LethalCCTV_Host") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)val); _host = val.AddComponent<SurveillanceBootstrap>(); _host.Initialize(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); Log.LogInfo((object)"LethalCCTV 1.0.1 loaded."); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LethalCCTV"; public const string PLUGIN_NAME = "LethalCCTV"; public const string PLUGIN_VERSION = "1.0.1"; } } namespace Y4NGZCompany.ShipSystems.Surveillance { [HarmonyPatch(typeof(HUDManager), "PingScan_performed")] internal static class CameraOperatorPingScanPatch { [HarmonyPrefix] private static bool Prefix(ref CallbackContext context) { if (!MonitorFocus.IsFocused) { return true; } if (Y4NGZPlayerAnimationBridge.IsFirstPersonHandEditModeActive) { return false; } if (((CallbackContext)(ref context)).performed) { MonitorFocus.TriggerCameraContextOrScan(); } return false; } } [HarmonyPatch(typeof(HUDManager), "UpdateScanNodes")] internal static class CameraOperatorScanNodesPatch { [HarmonyPrefix] private static bool Prefix() { return !MonitorFocus.IsFocused; } } [HarmonyPatch(typeof(PlayerControllerB), "ActivateItem_performed")] internal static class CameraOperatorActivateItemPatch { [HarmonyPrefix] private static bool Prefix() { return !MonitorFocus.IsFocused; } } [HarmonyPatch(typeof(PlayerControllerB), "ActivateItem_canceled")] internal static class CameraOperatorActivateItemCancelPatch { [HarmonyPrefix] private static bool Prefix() { return !MonitorFocus.IsFocused; } } [HarmonyPatch(typeof(PlayerControllerB), "Jump_performed")] internal static class CameraOperatorJumpPerformedPatch { [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance) { return !MonitorFocus.ShouldSuppressPlayerInput(__instance); } } [HarmonyPatch(typeof(PlayerControllerB), "Update")] internal static class CameraOperatorPlayerUpdatePatch { [HarmonyPrefix] private static void Prefix(PlayerControllerB __instance) { if (MonitorFocus.ShouldSuppressPlayerInput(__instance)) { MonitorFocus.ApplyFocusInputLock(__instance); } } } [HarmonyPatch(typeof(PlayerControllerB), "PlayerLookInput")] internal static class CameraOperatorPlayerLookInputPatch { [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance) { return !MonitorFocus.ShouldSuppressPlayerInput(__instance); } } internal static class CCTVAccessButton { private const string RootName = "LethalCCTV_AccessButton"; private const string TemplateChildName = "ButtonContainer/ButtonAnimContainer"; private const string RedButtonChildName = "RedButton"; private const string HoverTip = "View cameras : [E]"; private const string PressTriggerName = "press"; private const string GlassOpenBoolName = "GlassOpen"; private const float StateRefreshIntervalSeconds = 0.2f; private const float PressSfxVolume = 0.9f; private const string ScanNodeName = "LethalCCTV_AccessButtonScanNode"; private const string ScanNodeHeader = "CCTV Station"; private const string ScanNodeSubText = "View cameras"; private const int ScanNodeMaxRange = 8; private const int ScanNodeFallbackLayer = 22; private static GameObject _root; private static Animator _buttonAnimator; private static InteractTrigger _trigger; private static AudioSource _audio; private static AudioClip _pressSfx; private static UnityAction<PlayerControllerB> _listener; private static float _nextStateRefreshAt; private static bool _templateMissingLogged; private static bool _spawnLogged; internal static bool IsAvailable { get { if ((Object)(object)_root != (Object)null) { return (Object)(object)_trigger != (Object)null; } return false; } } internal static Transform Root { get { if (!((Object)(object)_root != (Object)null)) { return null; } return _root.transform; } } internal static Transform PressContactTransform { get { if (!((Object)(object)_trigger != (Object)null)) { return null; } return ((Component)_trigger).transform; } } internal static bool TryGetPressSurface(out Vector3 point, out Vector3 normal) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_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_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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: 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_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) point = Vector3.zero; normal = Vector3.up; Transform pressContactTransform = PressContactTransform; if ((Object)(object)pressContactTransform == (Object)null) { return false; } Vector3 bestAxis = pressContactTransform.up; float bestScore = Mathf.Abs(Vector3.Dot(bestAxis, Vector3.up)); ConsiderAxis(pressContactTransform.right); ConsiderAxis(pressContactTransform.forward); if (Vector3.Dot(bestAxis, Vector3.up) < 0f) { bestAxis = -bestAxis; } normal = ((((Vector3)(ref bestAxis)).sqrMagnitude > 0.0001f) ? ((Vector3)(ref bestAxis)).normalized : Vector3.up); Renderer val = ((Component)pressContactTransform).GetComponent<Renderer>(); if ((Object)(object)val == (Object)null) { val = ((Component)pressContactTransform).GetComponentInChildren<Renderer>(true); } if ((Object)(object)val == (Object)null) { point = pressContactTransform.position + normal * 0.015f; return true; } Bounds bounds = val.bounds; Vector3 extents = ((Bounds)(ref bounds)).extents; float num = Mathf.Abs(normal.x) * extents.x + Mathf.Abs(normal.y) * extents.y + Mathf.Abs(normal.z) * extents.z; point = ((Bounds)(ref bounds)).center + normal * num; return true; void ConsiderAxis(Vector3 candidate) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) float num2 = Mathf.Abs(Vector3.Dot(candidate, Vector3.up)); if (!(num2 <= bestScore)) { bestAxis = candidate; bestScore = num2; } } } internal static void Ensure(Transform stationRoot) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)stationRoot == (Object)null || (Object)(object)_root != (Object)null) { return; } AudioClip pressSfx; Transform val = ResolveTemplateButtonContainer(out pressSfx); if ((Object)(object)val == (Object)null) { if (!_templateMissingLogged) { _templateMissingLogged = true; ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)"[LethalCCTV] Access button template unavailable (teleporter unlockable prefab or ButtonContainer not found); red bezel button remains the CCTV entry."); } } return; } GameObject val2; try { val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, stationRoot, false); } catch (Exception ex) { if (!_templateMissingLogged) { _templateMissingLogged = true; ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogWarning((object)("[LethalCCTV] Access button clone failed: " + ex.Message)); } } return; } ((Object)val2).name = "LethalCCTV_AccessButton"; _root = val2; _pressSfx = pressSfx; StripClonedBehaviours(val2); _buttonAnimator = val2.GetComponentInChildren<Animator>(true); _audio = val2.AddComponent<AudioSource>(); _audio.playOnAwake = false; _audio.spatialBlend = 1f; _audio.minDistance = 0.8f; _audio.maxDistance = 12f; WireInteractTrigger(val2); CCTVOperatorStation.ApplyAccessButtonPlacement(val2.transform); BuildScanNode(val2); if ((Object)(object)_buttonAnimator != (Object)null) { _buttonAnimator.SetBool("GlassOpen", true); } if (_spawnLogged) { return; } _spawnLogged = true; ManualLogSource log3 = SurveillanceBootstrap.Log; if (log3 != null) { string[] obj = new string[8] { $"[LethalCCTV] Access button spawned from teleporter template: local={val2.transform.localPosition} ", "trigger=", ((Object)(object)_trigger != (Object)null) ? "wired" : "MISSING", " animator=", null, null, null, null }; object obj2; if (!((Object)(object)_buttonAnimator != (Object)null)) { obj2 = "MISSING"; } else { RuntimeAnimatorController runtimeAnimatorController = _buttonAnimator.runtimeAnimatorController; obj2 = ((runtimeAnimatorController != null) ? ((Object)runtimeAnimatorController).name : null) ?? "<none>"; } obj[4] = (string)obj2; obj[5] = " pressSfx="; obj[6] = (((Object)(object)_pressSfx != (Object)null) ? ((Object)_pressSfx).name : "<none>"); obj[7] = "."; log3.LogInfo((object)string.Concat(obj)); } } internal static void Shutdown() { if ((Object)(object)_trigger != (Object)null && _listener != null && _trigger.onInteract != null) { ((UnityEvent<PlayerControllerB>)(object)_trigger.onInteract).RemoveListener(_listener); } if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _buttonAnimator = null; _trigger = null; _audio = null; _pressSfx = null; _listener = null; _nextStateRefreshAt = 0f; _spawnLogged = false; } internal static void Tick() { if ((Object)(object)_trigger == (Object)null || Time.unscaledTime < _nextStateRefreshAt) { return; } _nextStateRefreshAt = Time.unscaledTime + 0.2f; if ((Object)(object)_buttonAnimator != (Object)null) { try { _buttonAnimator.SetBool("GlassOpen", true); } catch { } } if (CCTVMonitorFeedSync.IsEntryAvailable(out var unavailableReason)) { _trigger.interactable = true; _trigger.hoverTip = "View cameras : [E]"; } else { _trigger.interactable = false; _trigger.disabledHoverTip = unavailableReason ?? string.Empty; } } internal static void PlayPressAnimation() { if ((Object)(object)_buttonAnimator != (Object)null) { try { _buttonAnimator.SetBool("GlassOpen", true); _buttonAnimator.ResetTrigger("press"); _buttonAnimator.SetTrigger("press"); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Access button press animation failed: " + ex.Message)); } } } if ((Object)(object)_audio != (Object)null && (Object)(object)_pressSfx != (Object)null) { _audio.PlayOneShot(_pressSfx, 0.9f); } } private static void BuildScanNode(GameObject clone) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0044: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)_trigger != (Object)null) ? ((Component)_trigger).transform : clone.transform); GameObject val2 = new GameObject("LethalCCTV_AccessButtonScanNode"); val2.transform.SetParent(clone.transform, false); val2.transform.position = val.position; int num = LayerMask.NameToLayer("ScanNode"); val2.layer = ((num >= 0) ? num : 22); try { val2.tag = "DoNotSet"; } catch (UnityException) { } SphereCollider obj = val2.AddComponent<SphereCollider>(); ((Collider)obj).isTrigger = true; obj.radius = 0.3f; ScanNodeProperties obj2 = val2.AddComponent<ScanNodeProperties>(); obj2.headerText = "CCTV Station"; obj2.subText = "View cameras"; obj2.minRange = 0; obj2.maxRange = 8; obj2.requiresLineOfSight = false; obj2.nodeType = 0; obj2.scrapValue = 0; obj2.creatureScanID = -1; } private static void OnPressed(PlayerControllerB player) { CCTVVanillaMonitorButtons.RequestEntry(player); } private static Transform ResolveTemplateButtonContainer(out AudioClip pressSfx) { pressSfx = null; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.unlockablesList == (Object)null || instance.unlockablesList.unlockables == null) { return null; } for (int i = 0; i < instance.unlockablesList.unlockables.Count; i++) { GameObject val = instance.unlockablesList.unlockables[i]?.prefabObject; if ((Object)(object)val == (Object)null) { continue; } ShipTeleporter component = val.GetComponent<ShipTeleporter>(); if (!((Object)(object)component == (Object)null) && !component.isInverseTeleporter) { Transform val2 = val.transform.Find("ButtonContainer/ButtonAnimContainer"); if (!((Object)(object)val2 == (Object)null)) { pressSfx = component.buttonPressSFX; return val2; } } } return null; } private static void StripClonedBehaviours(GameObject clone) { MonoBehaviour[] componentsInChildren = clone.GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && (!(val is InteractTrigger) || !string.Equals(((Object)((Component)val).gameObject).name, "RedButton", StringComparison.Ordinal))) { Object.Destroy((Object)(object)val); } } Transform val2 = FindChildRecursive(clone.transform, "ButtonGlass"); if ((Object)(object)val2 != (Object)null) { Collider[] components = ((Component)val2).GetComponents<Collider>(); for (int j = 0; j < components.Length; j++) { Object.Destroy((Object)(object)components[j]); } } } private static void WireInteractTrigger(GameObject clone) { Transform val = FindChildRecursive(clone.transform, "RedButton"); _trigger = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<InteractTrigger>() : null); if ((Object)(object)_trigger == (Object)null) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)"[LethalCCTV] Access button clone has no RedButton InteractTrigger; entry stays on the bezel button."); } return; } if (_trigger.onInteract != null) { int persistentEventCount = ((UnityEventBase)_trigger.onInteract).GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { ((UnityEventBase)_trigger.onInteract).SetPersistentListenerState(i, (UnityEventCallState)0); } } _listener = OnPressed; ((UnityEvent<PlayerControllerB>)(object)_trigger.onInteract).AddListener(_listener); _trigger.hoverTip = "View cameras : [E]"; _trigger.interactable = true; } private static Transform FindChildRecursive(Transform root, string childName) { if ((Object)(object)root == (Object)null) { return null; } if (string.Equals(((Object)root).name, childName, StringComparison.Ordinal)) { return root; } for (int i = 0; i < root.childCount; i++) { Transform val = FindChildRecursive(root.GetChild(i), childName); if ((Object)(object)val != (Object)null) { return val; } } return null; } } internal static class CctvCommandTargetBridge { internal sealed class TargetInfo { internal Component Component; internal string DisplayName; internal string CommandHint; internal Vector3 Position; internal float Radius; } private const string ApiTypeName = "Y4NGZCompany.Facility.Security.CctvCommandTargetApi, LethalCCTV"; private static Type _apiType; private static bool _resolved; private static MethodInfo _describeTargetMethod; private static MethodInfo _getActiveTargetsMethod; private static MethodInfo _executeCommandMethod; internal static bool TryDescribeTarget(Component component, out TargetInfo info) { info = null; Resolve(); if (_describeTargetMethod == null || (Object)(object)component == (Object)null) { return false; } try { object descriptor = _describeTargetMethod.Invoke(null, new object[1] { component }); info = ReadDescriptor(descriptor); return info != null; } catch { return false; } } internal static List<TargetInfo> GetActiveTargets() { Resolve(); List<TargetInfo> list = new List<TargetInfo>(); if (_getActiveTargetsMethod == null) { return list; } try { if (!(_getActiveTargetsMethod.Invoke(null, Array.Empty<object>()) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { TargetInfo targetInfo = ReadDescriptor(item); if (targetInfo != null) { list.Add(targetInfo); } } } catch { } return list; } internal static bool TryExecuteCommand(Component component, string command, out string statusText) { statusText = "UNSUPPORTED TARGET"; Resolve(); if (_executeCommandMethod == null || (Object)(object)component == (Object)null) { return false; } try { object obj = _executeCommandMethod.Invoke(null, new object[2] { component, command ?? string.Empty }); if (obj == null) { statusText = "NO RESPONSE"; return false; } statusText = ReadString(obj, "StatusText", "COMMAND FAILED"); return ReadBool(obj, "Success", fallback: false); } catch { statusText = "COMMAND FAILED"; return false; } } internal static bool TryFindMainframeTarget(out Component target, out string displayName, out string commandHint) { target = null; displayName = "MAINFRAME"; commandHint = "hack"; Resolve(); if (_apiType == null) { return false; } try { object obj = null; PropertyInfo propertyInfo = _apiType.GetProperty("MainframeSupport", BindingFlags.Static | BindingFlags.Public) ?? _apiType.GetProperty("Mainframe", BindingFlags.Static | BindingFlags.Public); if (propertyInfo != null) { obj = propertyInfo.GetValue(null); } if (obj == null) { PropertyInfo propertyInfo2 = Type.GetType("Y4NGZCompany.Facility.Mainframe.MainframeSupport, LethalCCTV", throwOnError: false)?.GetProperty("Active", BindingFlags.Static | BindingFlags.Public); if (propertyInfo2 != null) { obj = propertyInfo2.GetValue(null); } } if (obj == null) { return false; } target = (Component)((obj is Component) ? obj : null); if ((Object)(object)target == (Object)null) { return false; } return true; } catch { return false; } } private static void Resolve() { if (!_resolved || !(_apiType != null)) { _resolved = true; _apiType = Type.GetType("Y4NGZCompany.Facility.Security.CctvCommandTargetApi, LethalCCTV", throwOnError: false); _describeTargetMethod = _apiType?.GetMethod("DescribeTarget", BindingFlags.Static | BindingFlags.Public); _getActiveTargetsMethod = _apiType?.GetMethod("GetActiveTargets", BindingFlags.Static | BindingFlags.Public); _executeCommandMethod = _apiType?.GetMethod("ExecuteCommand", BindingFlags.Static | BindingFlags.Public); } } private static TargetInfo ReadDescriptor(object descriptor) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (descriptor == null) { return null; } Component val = ReadComponent(descriptor, "Target"); if ((Object)(object)val == (Object)null) { return null; } return new TargetInfo { Component = val, DisplayName = ReadString(descriptor, "DisplayName", "SUPPORT NODE"), CommandHint = ReadString(descriptor, "CommandHint", string.Empty), Position = ReadVector3(descriptor, "Position", val.transform.position), Radius = Mathf.Clamp(ReadFloat(descriptor, "Radius", 0.9f), 0.35f, 4f) }; } private static Component ReadComponent(object target, string memberName) { object obj = ReadMember(target, memberName); return (Component)((obj is Component) ? obj : null); } private static string ReadString(object target, string memberName, string fallback) { return (ReadMember(target, memberName) as string) ?? fallback; } private static bool ReadBool(object target, string memberName, bool fallback) { object obj = ReadMember(target, memberName); if (obj is bool) { return (bool)obj; } return fallback; } private static float ReadFloat(object target, string memberName, float fallback) { object obj = ReadMember(target, memberName); if (obj is float) { return (float)obj; } if (obj is double num) { return (float)num; } return fallback; } private static Vector3 ReadVector3(object target, string memberName, Vector3 fallback) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) object obj = ReadMember(target, memberName); if (obj is Vector3) { return (Vector3)obj; } return fallback; } private static object ReadMember(object target, string memberName) { if (target == null) { return null; } Type type = target.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public); if (property != null) { return property.GetValue(target); } return type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public)?.GetValue(target); } } internal static class CCTVFocusControlsOverlay { private static GameObject _root; private static Canvas _canvas; private static Image _background; private static RectTransform _panelRect; private static RectTransform _textRect; private static TextMeshProUGUI _text; private static string _lastText; private const float PanelPaddingX = 18f; private const float PanelPaddingY = 12f; private const float PanelMinWidth = 260f; private const float PanelMaxWidth = 620f; private const float PanelMinHeight = 56f; private const string PlayerControlsText = "CCTV CONTROLS\nMOUSE AIM CAMERA\nARROWS CAMERA LIST\nSPACE RADAR VIEW\nSPC+WHL RADAR ZOOM\nLMB PING\nRMB TARGET\nV HOLD WALKIE\nE/ESC EXIT"; private const string DebugControlsText = "\nH HIDE PANEL\nF1 EDIT MENU\nF2 CAMERA EDIT"; internal static Transform RootTransform { get { if (!((Object)(object)_root != (Object)null)) { return null; } return _root.transform; } } private static bool ConfiguredOn { get { if (SurveillanceBootstrap.Config?.ShowOperatorControlsOverlay != null) { return SurveillanceBootstrap.Config.ShowOperatorControlsOverlay.Value; } return false; } } private static bool OverlayEnabled { get { if (!ConfiguredOn) { return CCTVStickyNoteItem.RegistrationFailed; } return true; } } internal static void Tick() { if (!OverlayEnabled) { Shutdown(); return; } if (!MonitorFocus.IsFocused) { SetVisible(visible: false); return; } Ensure(); SetVisible(visible: true); RefreshText(); } internal static void Shutdown() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _canvas = null; _background = null; _panelRect = null; _textRect = null; _text = null; _lastText = null; } private static void Ensure() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("LethalCCTV_FocusControlsOverlay", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); Object.DontDestroyOnLoad((Object)(object)_root); _canvas = _root.GetComponent<Canvas>(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 32760; CanvasScaler component = _root.GetComponent<CanvasScaler>(); component.uiScaleMode = (ScaleMode)1; component.referenceResolution = new Vector2(1920f, 1080f); component.screenMatchMode = (ScreenMatchMode)0; component.matchWidthOrHeight = 0.5f; ((Behaviour)_root.GetComponent<GraphicRaycaster>()).enabled = false; GameObject val = new GameObject("ControlsPanel", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(_root.transform, false); _panelRect = val.GetComponent<RectTransform>(); _panelRect.anchorMin = new Vector2(0f, 1f); _panelRect.anchorMax = new Vector2(0f, 1f); _panelRect.pivot = new Vector2(0f, 1f); _panelRect.anchoredPosition = new Vector2(26f, -86f); _panelRect.sizeDelta = new Vector2(260f, 56f); _background = val.GetComponent<Image>(); ((Graphic)_background).color = new Color(0f, 0f, 0f, 0.56f); ((Graphic)_background).raycastTarget = false; GameObject val2 = new GameObject("ControlsText", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI) }); val2.transform.SetParent(val.transform, false); _textRect = val2.GetComponent<RectTransform>(); _textRect.anchorMin = Vector2.zero; _textRect.anchorMax = Vector2.one; _textRect.offsetMin = new Vector2(18f, 12f); _textRect.offsetMax = new Vector2(-18f, -12f); _text = val2.GetComponent<TextMeshProUGUI>(); ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).enableWordWrapping = false; ((TMP_Text)_text).richText = true; ((TMP_Text)_text).alignment = (TextAlignmentOptions)257; ((TMP_Text)_text).fontSize = 24f; ((TMP_Text)_text).lineSpacing = -7f; ((Graphic)_text).color = new Color(1f, 0.58f, 0.14f, 0.96f); TryAssignHudFont(_text); } } private static void RefreshText() { if ((Object)(object)_text == (Object)null) { return; } if (MonitorFocus.IsOperatorDebugToolsEnabled) { string cameraPlacementEditOverlayText = MonitorFocus.GetCameraPlacementEditOverlayText(); if (!string.IsNullOrWhiteSpace(cameraPlacementEditOverlayText)) { SetText(cameraPlacementEditOverlayText); return; } string stationEditMenuOverlayText = MonitorFocus.GetStationEditMenuOverlayText(); if (!string.IsNullOrWhiteSpace(stationEditMenuOverlayText)) { SetText(stationEditMenuOverlayText); return; } string firstPersonHandEditOverlayText = Y4NGZPlayerAnimationBridge.GetFirstPersonHandEditOverlayText(); if (!string.IsNullOrWhiteSpace(firstPersonHandEditOverlayText)) { SetText(firstPersonHandEditOverlayText); } else { SetText("CCTV CONTROLS\nMOUSE AIM CAMERA\nARROWS CAMERA LIST\nSPACE RADAR VIEW\nSPC+WHL RADAR ZOOM\nLMB PING\nRMB TARGET\nV HOLD WALKIE\nE/ESC EXIT\nH HIDE PANEL\nF1 EDIT MENU\nF2 CAMERA EDIT"); } } else { SetText("CCTV CONTROLS\nMOUSE AIM CAMERA\nARROWS CAMERA LIST\nSPACE RADAR VIEW\nSPC+WHL RADAR ZOOM\nLMB PING\nRMB TARGET\nV HOLD WALKIE\nE/ESC EXIT"); } } private static void SetText(string value) { if (!(value == _lastText)) { _lastText = value; ((TMP_Text)_text).text = value; ResizePanelToText(); } } private static void ResizePanelToText() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_panelRect == (Object)null) && !((Object)(object)_text == (Object)null)) { ((TMP_Text)_text).ForceMeshUpdate(true, false); Vector2 preferredValues = ((TMP_Text)_text).GetPreferredValues(((TMP_Text)_text).text, 584f, 0f); float num = Mathf.Clamp(Mathf.Ceil(preferredValues.x + 36f), 260f, 620f); float num2 = Mathf.Max(56f, Mathf.Ceil(preferredValues.y + 24f)); _panelRect.sizeDelta = new Vector2(num, num2); if ((Object)(object)_textRect != (Object)null) { _textRect.offsetMin = new Vector2(18f, 12f); _textRect.offsetMax = new Vector2(-18f, -12f); } } } private static void SetVisible(bool visible) { if ((Object)(object)_root != (Object)null && _root.activeSelf != visible) { _root.SetActive(visible); } } private static void TryAssignHudFont(TextMeshProUGUI target) { if ((Object)(object)target == (Object)null) { return; } try { HUDManager instance = HUDManager.Instance; TextMeshProUGUI[] array = (((Object)(object)instance != (Object)null) ? instance.controlTipLines : null); if (array != null && array.Length != 0 && (Object)(object)array[0] != (Object)null && (Object)(object)((TMP_Text)array[0]).font != (Object)null) { ((TMP_Text)target).font = ((TMP_Text)array[0]).font; } } catch { } } } [DefaultExecutionOrder(32000)] internal sealed class CCTVLocalCameraPositionStabilizer : MonoBehaviour { private PlayerControllerB _player; private Transform _playerRoot; private Transform _cameraTransform; private Vector3 _playerLocalPosition; private int _releaseLateUpdates = -1; internal void Initialize(PlayerControllerB player, Transform playerRoot, Transform cameraTransform, Vector3 playerLocalPosition) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) _player = player; _playerRoot = playerRoot; _cameraTransform = cameraTransform; _playerLocalPosition = playerLocalPosition; _releaseLateUpdates = -1; ((Behaviour)this).enabled = true; } internal void ReleaseAfterLateUpdates(int lateUpdates) { _releaseLateUpdates = Mathf.Max(1, lateUpdates); ((Behaviour)this).enabled = true; } internal void ApplyNow() { //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) if (!((Object)(object)_playerRoot == (Object)null) && !((Object)(object)_cameraTransform == (Object)null)) { _cameraTransform.position = _playerRoot.TransformPoint(_playerLocalPosition); } } private void LateUpdate() { if (_releaseLateUpdates < 0) { return; } ApplyNow(); if (!((Object)(object)_player != (Object)null) || !Y4NGZPlayerAnimationBridge.IsLocalSessionActive(_player)) { _releaseLateUpdates--; if (_releaseLateUpdates <= 0) { ((Behaviour)this).enabled = false; Object.Destroy((Object)(object)this); } } } } [Serializable] public sealed class CCTVMarkerMessage { public int Id; public int Kind; public Vector3 Position; public float Radius; public int ColorKind; public string Label; public float Lifetime; public int CameraIndex; } internal static class CCTVMarkerManager { private sealed class MarkerVisual { public int Id; public int Kind; public Vector3 Position; public float Radius; public float ExpiresAt; public Color Color; public string Label; public RectTransform Root; public Image Top; public Image Bottom; public Image Left; public Image Right; public Image Dot; public TextMeshProUGUI Text; } private const float DEFAULT_SCAN_LIFETIME = 8f; private const float DEFAULT_PING_LIFETIME = 10f; private const float MAX_SCREEN_SIZE = 128f; private const float MIN_SCREEN_SIZE = 26f; private static readonly Color MARKER_GREEN = new Color(0.1f, 1f, 0.35f, 1f); private static readonly Color MARKER_RED = new Color(1f, 0.12f, 0.12f, 1f); private static GameObject _root; private static Canvas _canvas; private static LNetworkMessage<CCTVMarkerMessage> _message; private static readonly List<MarkerVisual> _markers = new List<MarkerVisual>(32); private static readonly HashSet<int> _seenIds = new HashSet<int>(); private static int _nextMarkerId; internal static bool HasActiveMarkers => _markers.Count > 0; internal static void Initialize() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("LethalCCTV_WorldMarkers"); if ((Object)(object)SurveillanceBootstrap.Instance != (Object)null) { _root.transform.SetParent(((Component)SurveillanceBootstrap.Instance).transform, false); } else { Object.DontDestroyOnLoad((Object)(object)_root); } _canvas = _root.AddComponent<Canvas>(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 135; CanvasScaler obj = _root.AddComponent<CanvasScaler>(); obj.uiScaleMode = (ScaleMode)1; obj.referenceResolution = new Vector2(1920f, 1080f); obj.matchWidthOrHeight = 0.5f; _message = LNetworkMessage<CCTVMarkerMessage>.Connect("cctv_marker_v1", (Action<CCTVMarkerMessage, ulong>)OnServerReceived, (Action<CCTVMarkerMessage>)OnClientReceived, (Action<CCTVMarkerMessage, ulong>)null); } } internal static void Shutdown() { ClearAll(); _message?.ClearSubscriptions(); _message = null; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; _canvas = null; } } internal static void ClearAll() { for (int i = 0; i < _markers.Count; i++) { if ((Object)(object)_markers[i]?.Root != (Object)null) { Object.Destroy((Object)(object)((Component)_markers[i].Root).gameObject); } } _markers.Clear(); _seenIds.Clear(); } internal static void PublishPing(Vector3 position, int cameraIndex) { //IL_0018: 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) Publish(new CCTVMarkerMessage { Id = NextId(), Kind = 1, Position = position, Radius = 0.55f, ColorKind = 0, Label = "CCTV PING", Lifetime = 10f, CameraIndex = cameraIndex }); } internal static void PublishContextMarker(Vector3 position, float radius, string label, int colorKind, int cameraIndex) { //IL_0018: 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) Publish(new CCTVMarkerMessage { Id = NextId(), Kind = 2, Position = position, Radius = Mathf.Clamp(radius, 0.35f, 4f), ColorKind = colorKind, Label = (string.IsNullOrEmpty(label) ? "CCTV TARGET" : label), Lifetime = 8f, CameraIndex = cameraIndex }); } internal static void Tick() { if ((Object)(object)_root == (Object)null) { return; } Camera camera = ResolveGameplayCamera(); bool isFocused = MonitorFocus.IsFocused; for (int num = _markers.Count - 1; num >= 0; num--) { MarkerVisual markerVisual = _markers[num]; if (markerVisual == null || Time.unscaledTime >= markerVisual.ExpiresAt) { if ((Object)(object)markerVisual?.Root != (Object)null) { Object.Destroy((Object)(object)((Component)markerVisual.Root).gameObject); } _markers.RemoveAt(num); } else { UpdateMarker(markerVisual, camera, isFocused); } } } private static int NextId() { int num = (int)(((Object)(object)GameNetworkManager.Instance?.localPlayerController != (Object)null) ? GameNetworkManager.Instance.localPlayerController.playerClientId : 0); _nextMarkerId++; return (num << 20) ^ _nextMarkerId ^ Mathf.RoundToInt(Time.realtimeSinceStartup * 1000f); } private static void Publish(CCTVMarkerMessage data) { AddLocalMarker(data); if (_message == null) { return; } try { _message.SendServer(data); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] CCTV marker stayed local: " + ex.Message)); } } } private static void OnServerReceived(CCTVMarkerMessage data, ulong _) { try { _message?.SendClients(data); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] CCTV marker server broadcast failed: " + ex.Message)); } } } private static void OnClientReceived(CCTVMarkerMessage data) { AddLocalMarker(data); } private static void AddLocalMarker(CCTVMarkerMessage data) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (data == null) { return; } Initialize(); if (!_seenIds.Contains(data.Id)) { _seenIds.Add(data.Id); MarkerVisual item = CreateMarkerVisual(data); _markers.Add(item); if (data.Kind == 0) { CCTVScanGlowManager.MarkScanTarget(data.Position, data.Radius, data.ColorKind, data.Lifetime); } } } private static MarkerVisual CreateMarkerVisual(CCTVMarkerMessage data) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_006b: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject($"CCTVMarker_{data.Id}"); val.transform.SetParent(_root.transform, false); RectTransform val2 = val.AddComponent<RectTransform>(); val2.anchorMin = new Vector2(0.5f, 0.5f); val2.anchorMax = new Vector2(0.5f, 0.5f); val2.pivot = new Vector2(0.5f, 0.5f); val2.sizeDelta = new Vector2(64f, 64f); Color color = ((data.ColorKind == 1) ? MARKER_RED : MARKER_GREEN); MarkerVisual markerVisual = new MarkerVisual { Id = data.Id, Kind = data.Kind, Position = data.Position, Radius = Mathf.Clamp(data.Radius, 0.25f, 4f), ExpiresAt = Time.unscaledTime + Mathf.Clamp(data.Lifetime, 1f, 15f), Color = color, Label = ((!string.IsNullOrEmpty(data.Label)) ? data.Label : ((data.Kind == 1) ? "PING" : "SCAN")), Root = val2 }; markerVisual.Top = CreateLine(val2, "Top", color); markerVisual.Bottom = CreateLine(val2, "Bottom", color); markerVisual.Left = CreateLine(val2, "Left", color); markerVisual.Right = CreateLine(val2, "Right", color); markerVisual.Dot = CreateLine(val2, "Dot", color); markerVisual.Text = CreateText(val2, "Label", markerVisual.Label, color); return markerVisual; } private static Image CreateLine(RectTransform parent, string name, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0039: 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_005d: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent((Transform)(object)parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0.5f, 0.5f); obj.anchorMax = new Vector2(0.5f, 0.5f); obj.pivot = new Vector2(0.5f, 0.5f); Image obj2 = val.AddComponent<Image>(); ((Graphic)obj2).color = color; ((Graphic)obj2).raycastTarget = false; return obj2; } private static TextMeshProUGUI CreateText(RectTransform parent, string name, string text, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0039: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent((Transform)(object)parent, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0.5f, 0f); obj.anchorMax = new Vector2(0.5f, 0f); obj.pivot = new Vector2(0.5f, 1f); obj.anchoredPosition = new Vector2(0f, -8f); obj.sizeDelta = new Vector2(160f, 24f); TextMeshProUGUI obj2 = val.AddComponent<TextMeshProUGUI>(); ((TMP_Text)obj2).text = text; ((TMP_Text)obj2).fontSize = 13f; ((TMP_Text)obj2).fontStyle = (FontStyles)1; ((TMP_Text)obj2).alignment = (TextAlignmentOptions)514; ((Graphic)obj2).color = color; ((TMP_Text)obj2).enableWordWrapping = false; ((Graphic)obj2).raycastTarget = false; return obj2; } private static void UpdateMarker(MarkerVisual marker, Camera camera, bool hide) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)marker?.Root == (Object)null) { return; } if (hide || (Object)(object)camera == (Object)null) { ((Component)marker.Root).gameObject.SetActive(false); return; } Vector3 val = camera.WorldToViewportPoint(marker.Position); if (val.z <= 0.05f || val.x < -0.08f || val.x > 1.08f || val.y < -0.08f || val.y > 1.08f) { ((Component)marker.Root).gameObject.SetActive(false); return; } ((Component)marker.Root).gameObject.SetActive(true); Transform transform = ((Component)_canvas).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); float num; Rect rect; if (!((Object)(object)val2 != (Object)null)) { num = Screen.width; } else { rect = val2.rect; num = ((Rect)(ref rect)).width; } float num2 = num; float num3; if (!((Object)(object)val2 != (Object)null)) { num3 = Screen.height; } else { rect = val2.rect; num3 = ((Rect)(ref rect)).height; } float num4 = num3; marker.Root.anchoredPosition = new Vector2((val.x - 0.5f) * num2, (val.y - 0.5f) * num4); float num5 = Mathf.Max(1f, Vector3.Distance(((Component)camera).transform.position, marker.Position)); float num6 = ((marker.Kind == 1) ? 34f : Mathf.Clamp(marker.Radius / num5 * 1150f, 26f, 128f)); marker.Root.sizeDelta = new Vector2(num6, num6); ApplyMarkerShape(marker, num6); } private static void ApplyMarkerShape(MarkerVisual marker, float size) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: 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_00dd: 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_01df: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) float num = ((marker.Kind == 1) ? 5f : 4f); float num2 = size * 0.5f; if (marker.Kind == 1) { float num3 = Mathf.Clamp(size * 0.32f, 8f, 14f); SetLine(marker.Top, new Vector2(0f, num2 - num3 * 0.5f), new Vector2(num, num3)); SetLine(marker.Bottom, new Vector2(0f, 0f - num2 + num3 * 0.5f), new Vector2(num, num3)); SetLine(marker.Left, new Vector2(0f - num2 + num3 * 0.5f, 0f), new Vector2(num3, num)); SetLine(marker.Right, new Vector2(num2 - num3 * 0.5f, 0f), new Vector2(num3, num)); SetLine(marker.Dot, Vector2.zero, new Vector2(5f, 5f)); } else { float num4 = Mathf.Clamp(size * 0.34f, 12f, 34f); SetLine(marker.Top, new Vector2(0f - num2 + num4 * 0.5f, num2), new Vector2(num4, num)); SetLine(marker.Left, new Vector2(0f - num2, num2 - num4 * 0.5f), new Vector2(num, num4)); SetLine(marker.Bottom, new Vector2(num2 - num4 * 0.5f, 0f - num2), new Vector2(num4, num)); SetLine(marker.Right, new Vector2(num2, 0f - num2 + num4 * 0.5f), new Vector2(num, num4)); SetLine(marker.Dot, Vector2.zero, new Vector2(4f, 4f)); } if ((Object)(object)marker.Text != (Object)null) { ((TMP_Text)marker.Text).rectTransform.anchoredPosition = new Vector2(0f, 0f - num2 - 10f); ((Graphic)marker.Text).color = new Color(marker.Color.r, marker.Color.g, marker.Color.b, 0.95f); } } private static void SetLine(Image image, Vector2 position, Vector2 size) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)image == (Object)null)) { RectTransform rectTransform = ((Graphic)image).rectTransform; rectTransform.anchoredPosition = position; rectTransform.sizeDelta = size; } } private static Camera ResolveGameplayCamera() { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && (Object)(object)val.gameplayCamera != (Object)null) { return val.gameplayCamera; } return Camera.main; } } [Serializable] public sealed class CCTVFeedStateMessage { public byte Kind; public int PlayerId; public byte FeedCctv; public int OperatorId; public int ActiveSlot = -1; } internal static class CCTVMonitorFeedSync { private const string CHANNEL = "y4ngz_cctv_feed_v1"; private const byte KindRequestClaim = 1; private const byte KindRequestRelease = 2; private const byte KindRequestToggle = 3; private const byte KindState = 4; private const byte KindRequestActiveSlot = 5; private const float ClaimTimeoutSeconds = 1.5f; private static LNetworkMessage<CCTVFeedStateMessage> _message; private static bool _initialized; private static bool _feedCctv; private static int _operatorId = -1; private static bool _serverFeedCctv; private static int _serverOperatorId = -1; private static int _serverActiveSlot = -1; private static PlayerControllerB _claimPlayer; private static Action _claimConfirmed; private static Action<string> _claimDenied; private static float _claimTimeoutAt; private static int _remoteActiveSlot = -1; private static int _lastSentActiveSlot = -1; internal static bool IsFeedCctv => _feedCctv; internal static bool HasOperator => _operatorId >= 0; internal static int OperatorId => _operatorId; internal static bool IsClaimPending => (Object)(object)_claimPlayer != (Object)null; internal static int RemoteActiveSlot => _remoteActiveSlot; internal static bool IsLocalOperator { get { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null) { return _operatorId == (int)val.playerClientId; } return false; } } internal static void Initialize() { if (_initialized) { return; } _initialized = true; try { _message = LNetworkMessage<CCTVFeedStateMessage>.Connect("y4ngz_cctv_feed_v1", (Action<CCTVFeedStateMessage, ulong>)OnServerReceived, (Action<CCTVFeedStateMessage>)OnClientReceived, (Action<CCTVFeedStateMessage, ulong>)null); ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)"[LethalCCTV] Monitor feed sync channel 'y4ngz_cctv_feed_v1' connected."); } } catch (Exception ex) { ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogWarning((object)("[LethalCCTV] Monitor feed sync unavailable (feed state stays local): " + ex.Message)); } _message = null; } CCTVStationEvents.ChairEntered += OnLocalChairEntered; CCTVStationEvents.ChairExited += OnLocalChairExited; } internal static void Shutdown() { if (_initialized) { _initialized = false; CCTVStationEvents.ChairEntered -= OnLocalChairEntered; CCTVStationEvents.ChairExited -= OnLocalChairExited; _message?.ClearSubscriptions(); _message = null; ResetLocal("shutdown"); } } internal static void Tick() { if (!((Object)(object)_claimPlayer == (Object)null) && !(Time.unscaledTime < _claimTimeoutAt)) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)"[LethalCCTV] Feed sync claim timed out; failing open into local entry."); } PlayerControllerB claimPlayer = _claimPlayer; Action claimConfirmed = _claimConfirmed; ClearPendingClaim(); ApplyState(feedCctv: true, GetPlayerId(claimPlayer), "claim-timeout-failopen"); claimConfirmed?.Invoke(); } } internal static bool IsEntryAvailable(out string unavailableReason) { if (!CCTVTerminalUnlockable.IsPurchased()) { unavailableReason = "Requires CCTV terminal upgrade"; return false; } if (!IsShipLandedForCctv()) { unavailableReason = "Comes online once the ship has landed"; return false; } if (HasOperator && !IsLocalOperator) { unavailableReason = "In use by another employee"; return false; } if (MonitorFocus.IsFocused || MonitorFocus.IsEntryPoseSettleActive || IsClaimPending) { unavailableReason = string.Empty; return false; } unavailableReason = null; return true; } internal static bool IsFeedToggleAvailable() { if (CCTVTerminalUnlockable.IsPurchased() && !HasOperator && !IsClaimPending && !MonitorFocus.IsFocused) { return IsShipLandedForCctv(); } return false; } internal static bool IsShipLandedForCctv() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null && instance.shipHasLanded && !instance.shipIsLeaving) { return !instance.inShipPhase; } return false; } internal static void RequestEntry(PlayerControllerB player, Action onConfirmed, Action<string> onDenied) { if ((Object)(object)player == (Object)null || (Object)(object)_claimPlayer != (Object)null) { return; } if (HasOperator && !IsLocalOperator) { onDenied?.Invoke("Another employee is on the cameras."); return; } int playerId = GetPlayerId(player); if (playerId < 0) { onDenied?.Invoke("Player not ready."); return; } if (_message == null) { ApplyState(feedCctv: true, playerId, "offline-claim"); onConfirmed?.Invoke(); return; } _claimPlayer = player; _claimConfirmed = onConfirmed; _claimDenied = onDenied; _claimTimeoutAt = Time.unscaledTime + 1.5f; SendRequest(new CCTVFeedStateMessage { Kind = 1, PlayerId = playerId }); } internal static void NotifyLocalActiveSlotChanged(int slot) { if (!IsLocalOperator || slot == _lastSentActiveSlot) { return; } _lastSentActiveSlot = slot; if (_message == null) { _remoteActiveSlot = slot; return; } try { _message.SendServer(new CCTVFeedStateMessage { Kind = 5, PlayerId = _operatorId, ActiveSlot = slot }); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Active-slot sync stayed local: " + ex.Message)); } } } internal static void RequestFeedToggle(PlayerControllerB player) { if (IsFeedToggleAvailable()) { int playerId = GetPlayerId(player); if (_message == null) { ApplyState(!_feedCctv, _operatorId, "offline-toggle"); return; } SendRequest(new CCTVFeedStateMessage { Kind = 3, PlayerId = playerId }); } } internal static void ResetForOrbit(string reason) { _serverFeedCctv = false; _serverOperatorId = -1; _serverActiveSlot = -1; ClearPendingClaim(); ApplyState(feedCctv: false, -1, reason, -1); } private static void OnLocalChairEntered(PlayerControllerB player) { int playerId = GetPlayerId(player); if (playerId >= 0 && _operatorId != playerId) { if (_message == null) { ApplyState(feedCctv: true, playerId, "late-claim-offline"); return; } SendRequest(new CCTVFeedStateMessage { Kind = 1, PlayerId = playerId }); } } private static void OnLocalChairExited(PlayerControllerB player) { ReleaseLocalEntryClaim(player, "chair-exited"); } internal static void ReleaseLocalEntryClaim(PlayerControllerB player, string reason) { int num = GetPlayerId(player); if (num < 0 && IsLocalOperator) { num = _operatorId; } ClearPendingClaim(); if (num < 0) { return; } ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)string.Format("[LethalCCTV] Releasing local CCTV entry claim ({0}) for player #{1}.", reason ?? "unspecified", num)); } if (_message == null) { if (_operatorId == num) { ApplyState(_feedCctv, -1, "offline-release-" + (reason ?? "unspecified")); } } else { SendRequest(new CCTVFeedStateMessage { Kind = 2, PlayerId = num }); } } private static void SendRequest(CCTVFeedStateMessage message) { try { _message.SendServer(message); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Feed sync request stayed local: " + ex.Message)); } switch (message.Kind) { case 1: { _ = _claimPlayer; Action claimConfirmed = _claimConfirmed; ClearPendingClaim(); ApplyState(feedCctv: true, message.PlayerId, "send-failed-claim"); claimConfirmed?.Invoke(); break; } case 2: if (_operatorId == message.PlayerId) { ApplyState(_feedCctv, -1, "send-failed-release"); } break; case 3: ApplyState(!_feedCctv, _operatorId, "send-failed-toggle"); break; } } } private static void OnServerReceived(CCTVFeedStateMessage data, ulong _) { if (data == null) { return; } switch (data.Kind) { default: return; case 1: if (_serverOperatorId < 0 || _serverOperatorId == data.PlayerId) { _serverOperatorId = data.PlayerId; _serverFeedCctv = true; _serverActiveSlot = -1; } break; case 2: if (_serverOperatorId == data.PlayerId) { _serverOperatorId = -1; } break; case 3: if (_serverOperatorId < 0) { _serverFeedCctv = !_serverFeedCctv; } break; case 5: if (_serverOperatorId == data.PlayerId) { _serverActiveSlot = data.ActiveSlot; } break; case 4: return; } BroadcastState(); } private static void BroadcastState() { try { _message?.SendClients(new CCTVFeedStateMessage { Kind = 4, PlayerId = -1, FeedCctv = (_serverFeedCctv ? ((byte)1) : ((byte)0)), OperatorId = _serverOperatorId, ActiveSlot = _serverActiveSlot }); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Feed sync broadcast failed: " + ex.Message)); } } } private static void OnClientReceived(CCTVFeedStateMessage data) { if (data != null && data.Kind == 4) { ApplyState(data.FeedCctv != 0, data.OperatorId, "network-state", data.ActiveSlot); } } private static void ApplyState(bool feedCctv, int operatorId, string reason, int? activeSlot = null) { bool num = _feedCctv != feedCctv; bool flag = _operatorId != operatorId; _feedCctv = feedCctv; _operatorId = operatorId; if (activeSlot.HasValue) { _remoteActiveSlot = activeSlot.Value; } if (operatorId < 0) { _lastSentActiveSlot = -1; } if (num || flag) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)("[LethalCCTV] Monitor feed state applied (" + reason + "): feed=" + (feedCctv ? "cctv" : "vanilla") + " operator=" + ((operatorId < 0) ? "none" : ("#" + operatorId)) + ".")); } } ApplyFeedToDisplay(); ResolvePendingClaim(); } internal static void ApplyFeedToDisplay() { bool flag = _feedCctv && CCTVTerminalUnlockable.IsPurchased(); if (!flag || !MonitorFocus.IsStationFeedFlipPending) { CCTVVanillaMonitorDisplay.SetCctvModeActive(flag, announce: false); if (flag) { CCTVVanillaMonitorDisplay.PrimeMonitorOwnershipNow(renderNow: false); } } } private static void ResolvePendingClaim() { if (!((Object)(object)_claimPlayer == (Object)null)) { int playerId = GetPlayerId(_claimPlayer); if (_operatorId == playerId && playerId >= 0) { Action claimConfirmed = _claimConfirmed; ClearPendingClaim(); claimConfirmed?.Invoke(); } else if (HasOperator) { Action<string> claimDenied = _claimDenied; ClearPendingClaim(); claimDenied?.Invoke("Another employee is on the cameras."); } } } private static void ClearPendingClaim() { _claimPlayer = null; _claimConfirmed = null; _claimDenied = null; _claimTimeoutAt = 0f; } private static void ResetLocal(string reason) { _serverFeedCctv = false; _serverOperatorId = -1; _serverActiveSlot = -1; ClearPendingClaim(); _feedCctv = false; _operatorId = -1; _remoteActiveSlot = -1; _lastSentActiveSlot = -1; ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Monitor feed state reset locally (" + reason + ").")); } } private static int GetPlayerId(PlayerControllerB player) { if (!((Object)(object)player != (Object)null)) { return -1; } return (int)player.playerClientId; } } [HarmonyPatch(typeof(ManualCameraRenderer), "SwitchScreenButton")] internal static class CCTVMonitorModeButtonPatch { [HarmonyPriority(600)] private static bool Prefix(ManualCameraRenderer __instance) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)__instance != (Object)(object)instance.mapScreen) { return true; } if (CCTVVanillaMonitorDisplay.IsGeneralImprovementsMonitorWallActive() && !CCTVVanillaMonitorDisplay.OwnsGeneralImprovementsMapScreen()) { return true; } return false; } } [HarmonyPatch] internal static class CCTVMonitorModeShipLeavePatch { [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] private static void OnShipLeave() { ResetForOrbit("ship-leave"); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "ShipLeaveAutomatically")] private static void OnShipLeaveAutomatically() { ResetForOrbit("ship-leave-auto"); } [HarmonyPostfix] [HarmonyPatch(typeof(GameNetworkManager), "Disconnect")] private static void OnDisconnect() { ResetForOrbit("disconnect"); } private static void ResetForOrbit(string reason) { MonitorFocus.ForceExit(reason); CCTVMonitorFeedSync.ResetForOrbit(reason); } } [HarmonyPatch(typeof(ManualCameraRenderer), "Update")] internal static class CCTVMonitorOwnershipManualCameraRendererUpdatePatch { private static bool Prefix(ManualCameraRenderer __instance) { if (!CCTVVanillaMonitorDisplay.ShouldSuppressVanillaMonitorRendererUpdate(__instance)) { return true; } CCTVVanillaMonitorDisplay.ReassertCctvScreenOwnershipAfterVanilla(); return false; } private static void Postfix() { CCTVVanillaMonitorDisplay.ReassertCctvScreenOwnershipAfterVanilla(); } } [HarmonyPatch(typeof(StartOfRound), "LateUpdate")] internal static class CCTVMonitorOwnershipStartOfRoundLateUpdatePatch { private static void Postfix() { CCTVVanillaMonitorDisplay.ReassertCctvScreenOwnershipAfterVanilla(); } } [Serializable] public sealed class CCTVOperatorAnimMessage { public byte Kind; public int PlayerId; public sbyte JoyX; public sbyte JoyY; public byte Arg; public int Yaw; } internal static class CCTVOperatorAnimSync { private const string CHANNEL = "y4ngz_cctv_anim_v1"; private const float JoystickSendInterval = 1f / 12f; private const float JoystickHeartbeatSeconds = 1f; private const float JoystickDiagnosticInterval = 1f; private const string MainframeDefaultStopTrigger = "SA_stopAnimation"; private const byte KindEnter = 1; private const byte KindExit = 2; private const byte KindCameraControl = 3; private const byte KindButtonPress = 4; private const byte KindSelectCamera = 5; private const byte KindRadarLook = 6; private const byte KindJoystick = 7; private const byte KindMainframeEnter = 8; private const byte KindMainframeExit = 9; private static LNetworkMessage<CCTVOperatorAnimMessage> _message; private static readonly Dictionary<int, OperatorAnimSession> _remoteSessions = new Dictionary<int, OperatorAnimSession>(); private static readonly HashSet<int> _remoteMainframePlayers = new HashSet<int>(); private static readonly List<int> _endedSessionIds = new List<int>(); private static readonly Dictionary<int, float> _nextRemoteJoystickDiagnosticAt = new Dictionary<int, float>(); private static readonly HashSet<int> _remoteJoystickDiagnosticLogged = new HashSet<int>(); private static float _nextJoystickSendAt; private static float _nextJoystickHeartbeatAt; private static float _nextLocalJoystickDiagnosticAt; private static sbyte _lastSentJoyX = sbyte.MaxValue; private static sbyte _lastSentJoyY = sbyte.MaxValue; private static bool _localJoystickDiagnosticLogged; internal static void Initialize() { if (_message != null) { return; } try { _message = LNetworkMessage<CCTVOperatorAnimMessage>.Connect("y4ngz_cctv_anim_v1", (Action<CCTVOperatorAnimMessage, ulong>)OnServerReceived, (Action<CCTVOperatorAnimMessage>)OnClientReceived, (Action<CCTVOperatorAnimMessage, ulong>)null); ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)"[LethalCCTV] Operator anim sync channel 'y4ngz_cctv_anim_v1' connected."); } } catch (Exception ex) { ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogWarning((object)("[LethalCCTV] Operator anim sync unavailable (animations stay local): " + ex.Message)); } _message = null; } } internal static void Shutdown() { foreach (KeyValuePair<int, OperatorAnimSession> remoteSession in _remoteSessions) { remoteSession.Value?.EndImmediate("sync-shutdown"); } _remoteSessions.Clear(); foreach (int item in new List<int>(_remoteMainframePlayers)) { ApplyRemoteMainframeExit(item, ResolvePlayer(item), "sync-shutdown"); } _remoteMainframePlayers.Clear(); _nextRemoteJoystickDiagnosticAt.Clear(); _remoteJoystickDiagnosticLogged.Clear(); _message?.ClearSubscriptions(); _message = null; } internal static void Tick() { if (_remoteSessions.Count == 0) { return; } _endedSessionIds.Clear(); foreach (KeyValuePair<int, OperatorAnimSession> remoteSession in _remoteSessions) { OperatorAnimSession value = remoteSession.Value; if (value == null) { _endedSessionIds.Add(remoteSession.Key); continue; } value.Tick(); if (!value.IsActive && !value.IsWindingDown) { _endedSessionIds.Add(remoteSession.Key); } } for (int i = 0; i < _endedSessionIds.Count; i++) { _remoteSessions.Remove(_endedSessionIds[i]); } } internal static void TickLocalJoystickSend(OperatorAnimSession localSession) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (_message != null && localSession != null && localSession.IsActive && localSession.CameraControlActive && !(Time.unscaledTime < _nextJoystickSendAt)) { sbyte b = QuantizeAxis(localSession.JoystickCommand.x); sbyte b2 = QuantizeAxis(localSession.JoystickCommand.y); bool num = b != _lastSentJoyX || b2 != _lastSentJoyY; bool flag = Time.unscaledTime >= _nextJoystickHeartbeatAt; if (num || flag) { _nextJoystickSendAt = Time.unscaledTime + 1f / 12f; _nextJoystickHeartbeatAt = Time.unscaledTime + 1f; _lastSentJoyX = b; _lastSentJoyY = b2; Send(new CCTVOperatorAnimMessage { Kind = 7, PlayerId = GetPlayerId(localSession.Player), JoyX = b, JoyY = b2 }); } } } internal static void SendEnter(PlayerControllerB player, int activeSlot) { _lastSentJoyX = sbyte.MaxValue; _lastSentJoyY = sbyte.MaxValue; _localJoystickDiagnosticLogged = false; _nextLocalJoystickDiagnosticAt = 0f; Send(new CCTVOperatorAnimMessage { Kind = 1, PlayerId = GetPlayerId(player), Arg = (byte)Mathf.Clamp(activeSlot, 0, 255) }); ReportOperatorNoiseToBundy(player); } internal static void SendExit(PlayerControllerB player) { Send(new CCTVOperatorAnimMessage { Kind = 2, PlayerId = GetPlayerId(player) }); } internal static void SendCameraControl(PlayerControllerB player, bool controlling) { Send(new CCTVOperatorAnimMessage { Kind = 3, PlayerId = GetPlayerId(player), Arg = (controlling ? ((byte)1) : ((byte)0)) }); } internal static void SendButtonPress(PlayerControllerB player, int buttonId) { Send(new CCTVOperatorAnimMessage { Kind = 4, PlayerId = GetPlayerId(player), Arg = (byte)Mathf.Clamp(buttonId, 0, 255) }); } internal static void SendSelectCamera(PlayerControllerB player, int slot) { Send(new CCTVOperatorAnimMessage { Kind = 5, PlayerId = GetPlayerId(player), Arg = (byte)Mathf.Clamp(slot, 0, 255) }); } internal static void SendRadarLook(PlayerControllerB player, bool looking) { Send(new CCTVOperatorAnimMessage { Kind = 6, PlayerId = GetPlayerId(player), Arg = (looking ? ((byte)1) : ((byte)0)) }); } internal static void SendMainframeEnter(PlayerControllerB player, short yaw) { Send(new CCTVOperatorAnimMessage { Kind = 8, PlayerId = GetPlayerId(player), Yaw = yaw }); ReportOperatorNoiseToBundy(player); } private static void ReportOperatorNoiseToBundy(PlayerControllerB player) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null)) { BundyWorldNoiseBridge.Report(((Component)player).transform.position, 8, player); } } internal static void SendMainframeExit(PlayerControllerB player) { Send(new CCTVOperatorAnimMessage { Kind = 9, PlayerId = GetPlayerId(player) }); } private static void Send(CCTVOperatorAnimMessage message) { if (message == null) { return; } if (message.PlayerId < 0) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)("[LethalCCTV] Operator anim sync send skipped: invalid player id for " + KindName(message.Kind) + ".")); } return; } LogLocalSend(message, _message != null); if (_message == null) { return; } try { _message.SendServer(message); } catch (Exception ex) { ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogDebug((object)("[LethalCCTV] Operator anim message stayed local: " + ex.Message)); } } } private static int GetPlayerId(PlayerControllerB player) { if (!((Object)(object)player != (Object)null)) { return -1; } return (int)player.playerClientId; } private static PlayerControllerB ResolvePlayer(int playerId) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null) { return null; } if (playerId < 0 || playerId >= instance.allPlayerScripts.Length) { return null; } return instance.allPlayerScripts[playerId]; } private static sbyte QuantizeAxis(float value) { return (sbyte)Mathf.RoundToInt(Mathf.Clamp(value, -1f, 1f) * 127f); } private static void OnServerReceived(CCTVOperatorAnimMessage data, ulong _) { try { _message?.SendClients(data); } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogDebug((object)("[LethalCCTV] Operator anim server broadcast failed: " + ex.Message)); } } } private static void OnClientReceived(CCTVOperatorAnimMessage data) { //IL_01b4: Unknown result type (might be due to invalid IL or missing references) if (data == null) { return; } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && data.PlayerId == (int)val.playerClientId) { return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null || data.PlayerId < 0 || data.PlayerId >= instance.allPlayerScripts.Length) { return; } PlayerControllerB val2 = instance.allPlayerScripts[data.PlayerId]; if ((Object)(object)val2 == (Object)null) { return; } LogRemoteReceive(data); switch (data.Kind) { case 1: ApplyRemoteEnter(data.PlayerId, val2, data.Arg); break; case 2: { if (_remoteSessions.TryGetValue(data.PlayerId, out var value)) { value.BeginGracefulEnd("remote-exit"); } _nextRemoteJoystickDiagnosticAt.Remove(data.PlayerId); _remoteJoystickDiagnosticLogged.Remove(data.PlayerId); break; } case 3: GetRemoteSession(data.PlayerId)?.SetCameraControl(data.Arg != 0); break; case 4: GetRemoteSession(data.PlayerId)?.PressButton(data.Arg, isStandUp: false); break; case 5: GetRemoteSession(data.PlayerId)?.SelectCamera(data.Arg); break; case 6: GetRemoteSession(data.PlayerId)?.SetRadarLook(data.Arg != 0); break; case 7: GetRemoteSession(data.PlayerId)?.SetJoystickTargetAbsolute(new Vector2((float)data.JoyX / 127f, (float)data.JoyY / 127f)); break; case 8: ApplyRemoteMainframeEnter(data.PlayerId, val2, data.Yaw); break; case 9: ApplyRemoteMainframeExit(data.PlayerId, val2, "remote-exit"); break; } } private static void ApplyRemoteEnter(int playerId, PlayerControllerB player, int activeSlot) { if (_remoteSessions.TryGetValue(playerId, out var value)) { value?.EndImmediate("remote-reenter"); _remoteSessions.Remove(playerId); } _nextRemoteJoystickDiagnosticAt.Remove(playerId); _remoteJoystickDiagnosticLogged.Remove(playerId); OperatorAnimSession operatorAnimSession = new OperatorAnimSession(player, isLocal: false); if (operatorAnimSession.Begin(Y4NGZPlayerAnimationBridge.ResolveControllerShared(), enterFlourish: true)) { operatorAnimSession.SetActiveSlotQuiet(activeSlot); _remoteSessions[playerId] = operatorAnimSession; ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)$"[LethalCCTV] Remote operator anim session started for player #{playerId}."); } ReportOperatorNoiseToBundy(player); } } private static void ApplyRemoteMainframeEnter(int playerId, PlayerControllerB player, int yaw) { if ((Object)(object)player == (Object)null) { return; } ResolveVanillaTerminalAnimation(out var enterTrigger, out var _); short num = (short)Mathf.RoundToInt(Mathf.Repeat((float)yaw, 360f)); try { player.UpdateSpecialAnimationValue(true, num, 0f, false); player.inSpecialInteractAnimation = true; player.enteringSpecialAnimation = false; } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)$"[LethalCCTV][MainframeInteract] Remote mainframe enter failed for player #{playerId}: {ex.Message}"); } } TryTriggerPlayerAnimator(player, enterTrigger, "mainframe-enter"); _remoteMainframePlayers.Add(playerId); ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogInfo((object)$"[LethalCCTV][MainframeInteract] Remote mainframe TypeOnTerminal stance entered for player #{playerId} yaw={num}."); } ReportOperatorNoiseToBundy(player); } private static void ApplyRemoteMainframeExit(int playerId, PlayerControllerB player, string reason) { if ((Object)(object)player != (Object)null) { ResolveVanillaTerminalAnimation(out var _, out var stopTrigger); TryTriggerPlayerAnimator(player, stopTrigger, "mainframe-exit"); try { player.UpdateSpecialAnimationValue(false, (short)0, 0f, false); player.inSpecialInteractAnimation = false; player.enteringSpecialAnimation = false; } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)$"[LethalCCTV][MainframeInteract] Remote mainframe exit failed for player #{playerId}: {ex.Message}"); } } } _remoteMainframePlayers.Remove(playerId); ManualLogSource log2 = SurveillanceBootstrap.Log; if (log2 != null) { log2.LogInfo((object)string.Format("[LethalCCTV][MainframeInteract] Remote mainframe TypeOnTerminal stance exited for player #{0} reason='{1}'.", playerId, reason ?? "unspecified")); } } private static void ResolveVanillaTerminalAnimation(out string enterTrigger, out string stopTrigger) { enterTrigger = null; stopTrigger = "SA_stopAnimation"; try { Terminal val = Object.FindObjectOfType<Terminal>(); InteractTrigger val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<InteractTrigger>() : null); if (!((Object)(object)val2 == (Object)null)) { if (!string.IsNullOrWhiteSpace(val2.animationString)) { enterTrigger = val2.animationString; } if (!string.IsNullOrWhiteSpace(val2.stopAnimationString)) { stopTrigger = val2.stopAnimationString; } } } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)("[LethalCCTV][MainframeInteract] Remote vanilla terminal animation probe failed: " + ex.Message)); } } } private static void TryTriggerPlayerAnimator(PlayerControllerB player, string trigger, string phase) { if ((Object)(object)player == (Object)null || (Object)(object)player.playerBodyAnimator == (Object)null || string.IsNullOrWhiteSpace(trigger)) { return; } try { if (AnimatorHasTrigger(player.playerBodyAnimator, trigger)) { player.playerBodyAnimator.ResetTrigger(trigger); player.playerBodyAnimator.SetTrigger(trigger); } } catch (Exception ex) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogWarning((object)("[LethalCCTV][MainframeInteract] Remote animator " + phase + " trigger '" + trigger + "' failed: " + ex.Message)); } } } private static bool AnimatorHasTrigger(Animator animator, string trigger) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 if ((Object)(object)animator == (Object)null || string.IsNullOrWhiteSpace(trigger)) { return false; } try { AnimatorControllerParameter[] parameters = animator.parameters; for (int i = 0; i < parameters.Length; i++) { if ((int)parameters[i].type == 9 && string.Equals(parameters[i].name, trigger, StringComparison.Ordinal)) { return true; } } } catch { } return false; } private static OperatorAnimSession GetRemoteSession(int playerId) { if (!_remoteSessions.TryGetValue(playerId, out var value) || !value.IsActive) { return null; } return value; } private static void LogLocalSend(CCTVOperatorAnimMessage message, bool channelReady) { if (message.Kind != 7 || ShouldLogLocalJoystick(message)) { ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)$"[LethalCCTV] Operator anim sync SEND {FormatMessage(message)} channelReady={channelReady}."); } } } private static bool ShouldLogLocalJoystick(CCTVOperatorAnimMessage message) { if (message.Kind != 7) { return true; } if (!_localJoystickDiagnosticLogged || Time.unscaledTime >= _nextLocalJoystickDiagnosticAt) { _localJoystickDiagnosticLogged = true; _nextLocalJoystickDiagnosticAt = Time.unscaledTime + 1f; return true; } return false; } private static void LogRemoteReceive(CCTVOperatorAnimMessage message) { if (message.Kind == 7) { bool num = _remoteJoystickDiagnosticLogged.Contains(message.PlayerId); _nextRemoteJoystickDiagnosticAt.TryGetValue(message.PlayerId, out var value); if (num && Time.unscaledTime < value) { return; } _remoteJoystickDiagnosticLogged.Add(message.PlayerId); _nextRemoteJoystickDiagnosticAt[message.PlayerId] = Time.unscaledTime + 1f; } ManualLogSource log = SurveillanceBootstrap.Log; if (log != null) { log.LogInfo((object)("[LethalCCTV] Operator anim sync RECV " + FormatMessage(message) + ".")); } } private static string FormatMessage(CCTVOperatorAnimMessage message) { if (message == null) { return "<null>"; } if (message.Kind == 7) { return $"{KindName(message.Kind)} player=#{message.PlayerId} " + $"joy=({(float)message.JoyX / 127f:F2},{(float)message.JoyY / 127f:F2})"; } if (message.Kind == 8) { return $"{KindName(message.Kind)} player=#{message.PlayerId} yaw={message.Yaw}"; } return $"{KindName(message.Kind)} player=#{message.PlayerId} arg={message.Arg}"; } private static string KindName(byte kind) { return kind switch { 1 => "Enter", 2 => "Exit", 3 => "CameraControl", 4 => "ButtonPress", 5 => "SelectCamera", 6 => "RadarLook", 7 => "Joystick", 8 => "MainframeEnter", 9 => "MainframeExit", _ => "Unknown(" + kind + ")", }; } } [HarmonyPatch(typeof(PlayerControllerB), "UpdatePlayerAnimationsToOtherClients")] internal static class CCTVOperatorAnimSyncSuppressPatch { private static bool Prefix(PlayerControllerB __instance) { return !Y4NGZPlayerAnimationBridge.IsLocalSessionActive(__instance); } } internal static class CCTVOperatorInteractionsBridge { private sealed class CctvOperatorManifest { public string interactionId; public string bundleInternalName; public CctvLocalViewmodelManifest localViewmodel; public CctvBodyManifest body; } private sealed class CctvLocalViewmodelManifest { public string bundleFileName; public string prefabAssetName; public string controllerAssetName; } private sealed class CctvBodyManifest { public bool enabled; public string bundleFileName; public string controllerAssetName; } private static class ManifestJson { internal static bool TryParseObject(string json, out Dictionary<string, object> value, out string error) { value = null; if (string.IsNullOrWhiteSpace(json)) { error = "detail='empty document'"; return false; } try { int index = 0; object obj = ParseValue(json, ref index); SkipWhitespace(json, ref index); if (index != json.Length) { throw new FormatException($"trailing content at offset {index}"); } value = obj as Dictionary<string, object>; if (value == null) { error = "detail='document root is not a JSON object'"; return false; } error = string.Empty; return true; } catch (Exception ex) { error = "detail='" + ex.GetType().Name + ": " + ex.Message + "'"; return false; } } internal static bool TryReadObject(Dictionary<string, object> owner, string key, out Dictionary<string, object> value) { value = ((owner != null && owner.TryGetValue(key, out var value2)) ? (value2 as Dictionary<string, object>) : null); return value != null; } internal static string ReadString(Dictionary<string, object> owner, string key) { if (owner == null || !owner.TryGetValue(key, out var value)) { return null; } return value as string; } internal static bool ReadBool(Dictionary<string, object> owner, string key) { bool flag = default(bool); int num; if (owner != null && owner.TryGetValue(key, out var value)) { if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static object ParseValue(string json, ref int index) { SkipWhitespace(json, ref index); if (index >= json.Length) { throw new FormatException("unexpected end of document"); } return json[index] switch { '{' => ParseObject(json, ref index), '[' => ParseArray(json, ref index), '"' => ParseString(json, ref index), _ => ParseLiteral(json, ref index), }; } private static Dictionary<string, object> ParseObject(string json, ref int index) { Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); index++; SkipWhitespace(json, ref index); if (index < json.Length && json[index] == '}') { index++; return dictionary; } while (true)