using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LethalFaceTime.Bridges; using LethalFaceTime.Runtime; using Microsoft.CodeAnalysis; using OpenBodyCams; using OpenBodyCams.API; using Scoops.gameobjects; using Scoops.misc; using Scoops.service; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Animations.Rigging; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalFaceTime")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Shows a live video feed of whoever you are on the phone with, on the phone screen.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LethalFaceTime")] [assembly: AssemblyTitle("LethalFaceTime")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] 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 LethalFaceTime { internal sealed class FaceTimeConfig { public readonly ConfigEntry Enabled; public readonly ConfigEntry ResolutionWidth; public readonly ConfigEntry ResolutionHeight; public readonly ConfigEntry Framerate; public readonly ConfigEntry ScreenOpacity; public readonly ConfigEntry ScreenInset; public readonly ConfigEntry DrawBehindPhoneWidgets; public readonly ConfigEntry ScreenLayerOverride; public readonly ConfigEntry PauseFeedWhenPhoneStowed; public readonly ConfigEntry TeardownBehaviour; public readonly ConfigEntry IdleKeepAliveSeconds; public readonly ConfigEntry DefaultFacing; public readonly ConfigEntry FlipCameraKey; public readonly ConfigEntry FrontDistance; public readonly ConfigEntry FrontHeightOffset; public readonly ConfigEntry FrontLateralOffset; public readonly ConfigEntry FrontYawAngle; public readonly ConfigEntry FrontPitchAngle; public readonly ConfigEntry PhoneFollowStrength; public readonly ConfigEntry PhoneLensClearance; public readonly ConfigEntry MountCameraOnPhone; public readonly ConfigEntry SelfieFieldOfView; public readonly ConfigEntry RearForwardOffset; public readonly ConfigEntry SubjectFraming; public readonly ConfigEntry SwitchboardScreenOffset; public readonly ConfigEntry EnableSwitchboardMonitor; public readonly ConfigEntry FullscreenResolutionWidth; public readonly ConfigEntry FullscreenResolutionHeight; public readonly ConfigEntry FullscreenFramerate; public readonly ConfigEntry MonitorKnobSpacing; public readonly ConfigEntry MonitorKnobScale; public readonly ConfigEntry MonitorKnobOutwardOffset; public readonly ConfigEntry LogSwitchboardHierarchy; public readonly ConfigEntry HidePhoneWidgetsDuringCall; public readonly ConfigEntry HideFirstPersonPhoneFromCalls; public readonly ConfigEntry LetterboxPhoneVideo; public readonly ConfigEntry ScreenTint; public readonly ConfigEntry ScreenTintStrength; public readonly ConfigEntry ScreenEffectStrength; public readonly ConfigEntry NightVisionStrength; public readonly ConfigEntry SwitchboardScreenInset; public readonly ConfigEntry RequireBodyCamShipUpgrade; public readonly ConfigEntry InterferenceDegradesVideo; public readonly ConfigEntry ShowVideoForNonPlayerCallers; public readonly ConfigEntry ReleaseOrphanedRenderTextures; public readonly ConfigEntry ReconcileInterval; public readonly ConfigEntry VerboseLogging; public Vector2Int Resolution => new Vector2Int(ResolutionWidth.Value, ResolutionHeight.Value); public Vector2Int FullscreenResolution { get { //IL_006f: Unknown result type (might be due to invalid IL or missing references) int value = FullscreenResolutionHeight.Value; int num = FullscreenResolutionWidth.Value; if (num <= 0) { float num2 = ((ResolutionHeight.Value > 0) ? ((float)ResolutionWidth.Value / (float)ResolutionHeight.Value) : 1.3333334f); num = Mathf.RoundToInt((float)value * num2); } return new Vector2Int(Mathf.Clamp(num, 64, 1920), Mathf.Clamp(value, 64, 1080)); } } public FaceTimeConfig(ConfigFile file) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Expected O, but got Unknown //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Expected O, but got Unknown //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0360: Expected O, but got Unknown //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Expected O, but got Unknown //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Expected O, but got Unknown //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Expected O, but got Unknown //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_0444: Expected O, but got Unknown //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Expected O, but got Unknown //IL_04ac: Unknown result type (might be due to invalid IL or missing references) //IL_04b6: Expected O, but got Unknown //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Expected O, but got Unknown //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Expected O, but got Unknown //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_0575: Expected O, but got Unknown //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Expected O, but got Unknown //IL_05da: Unknown result type (might be due to invalid IL or missing references) //IL_05e4: Expected O, but got Unknown //IL_0613: Unknown result type (might be due to invalid IL or missing references) //IL_061d: Expected O, but got Unknown //IL_064c: Unknown result type (might be due to invalid IL or missing references) //IL_0656: Expected O, but got Unknown //IL_0685: Unknown result type (might be due to invalid IL or missing references) //IL_068f: Expected O, but got Unknown //IL_071f: Unknown result type (might be due to invalid IL or missing references) //IL_075d: Unknown result type (might be due to invalid IL or missing references) //IL_0767: Expected O, but got Unknown //IL_0796: Unknown result type (might be due to invalid IL or missing references) //IL_07a0: Expected O, but got Unknown //IL_07cf: Unknown result type (might be due to invalid IL or missing references) //IL_07d9: Expected O, but got Unknown //IL_0808: Unknown result type (might be due to invalid IL or missing references) //IL_0812: Expected O, but got Unknown Enabled = file.Bind("General", "Enabled", true, "Master switch. When false the mod never creates a camera and never touches the phone model."); ReconcileInterval = file.Bind("General", "ReconcileInterval", 0.25f, new ConfigDescription("Seconds between safety-net checks of the local phone's call state. LethalPhones exposes no call events, so the mod hooks PlayerPhone.UpdateCallingUI for instant reaction and uses this low-frequency poll to catch anything the hook misses (disconnects, round end, ownership loss).", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 2f), Array.Empty())); VerboseLogging = file.Bind("General", "VerboseLogging", false, "Log every call/camera state transition. Useful when reporting a bug."); ResolutionWidth = file.Bind("Video", "ResolutionWidth", 256, new ConfigDescription("Width of the video feed in pixels. OpenBodyCams' own default is 160x120; a phone screen is small so there is little reason to go high.", (AcceptableValueBase)(object)new AcceptableValueRange(32, 1024), Array.Empty())); ResolutionHeight = file.Bind("Video", "ResolutionHeight", 192, new ConfigDescription("Height of the video feed in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange(32, 1024), Array.Empty())); Framerate = file.Bind("Video", "Framerate", 24f, new ConfigDescription("Frames per second the phone camera renders at. This maps onto OpenBodyCams' own frame limiter, so lowering it is a direct, real performance saving. 0 means 'render every frame'.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); ScreenOpacity = file.Bind("Screen", "Opacity", 1f, new ConfigDescription("Opacity of the video layer on the phone screen.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 1f), Array.Empty())); ScreenInset = file.Bind("Screen", "Inset", 0f, new ConfigDescription("Padding in canvas units between the edge of the phone's screen canvas and the video. Increase if the video overlaps artwork you want to keep visible.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 200f), Array.Empty())); DrawBehindPhoneWidgets = file.Bind("Screen", "DrawBehindPhoneWidgets", true, "True: the video is the bottom layer of the phone canvas, so the dialled number, status text and signal icons stay readable on top of it. False: the video covers them."); ScreenLayerOverride = file.Bind("Screen", "LayerOverride", -1, new ConfigDescription("-1 keeps the video on the same Unity layer as the phone's screen canvas (recommended). Set to 23 to put it on the layer OpenBodyCams itself uses to hide first-person geometry from body cams - do that only if you actually see a phone screen recursively mirrored inside a body cam feed.", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 31), Array.Empty())); PauseFeedWhenPhoneStowed = file.Bind("Performance", "PauseFeedWhenPhoneStowed", true, "Stop rendering the camera while the phone is flipped shut. The call stays connected; only the camera sleeps. Strongly recommended."); TeardownBehaviour = file.Bind("Performance", "TeardownBehaviour", TeardownMode.DisableOnly, "What to do with the OpenBodyCams camera when a call ends.\nDisableOnly - switch the camera off and keep the object for the rest of the session. Default, and recommended: disabling makes OpenBodyCams set Camera.enabled = false, which is a true zero per-frame cost, so the only thing retained is one small render texture. Every call after the first is also instant.\nDisableThenDestroyWhenIdle - as above, then destroy the camera after IdleKeepAliveSeconds.\nDestroyImmediately - free everything the moment the call drops. Lowest idle memory, but the next call pays to rebuild the camera.\nNote on the two destroying modes: OpenBodyCams creates one HDRP/Unlit material per camera and never destroys it, and that field is internal so this mod cannot destroy it either. Each rebuild therefore leaks one small material. Harmless over a few calls, but it is why DisableOnly is the default rather than the tidiest-looking option."); IdleKeepAliveSeconds = file.Bind("Performance", "IdleKeepAliveSeconds", 30f, new ConfigDescription("Only used by DisableThenDestroyWhenIdle.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 600f), Array.Empty())); InterferenceDegradesVideo = file.Bind("Flavour", "InterferenceDegradesVideo", true, "Fade the video towards black as LethalPhones' own connection interference rises, so a bad line looks as bad as it sounds."); DefaultFacing = file.Bind("Camera", "DefaultFacing", CameraFacing.Front, "Which way the phone camera points when a call starts.\nFront - the selfie view: the camera floats at arm's length in front of the other person, looking back at them, so it reads as though they are holding the phone up.\nRear - their point of view, i.e. an ordinary body cam looking out at whatever they are looking at."); FlipCameraKey = file.Bind("Camera", "FlipCameraKey", (Key)36, "Key that flips between the front and rear camera mid-call, like the button on a real phone. Ignored while typing in chat or using the terminal."); PhoneFollowStrength = file.Bind("Camera", "PhoneFollowStrength", 0.65f, new ConfigDescription("How much the camera rides the handset rather than the subject's look direction. The handset carries the walk bob and the hand animation, which is what makes the shot feel hand-held; the look direction keeps the face towards the lens even when the arm lags. 0 is a rigid head cam, 1 follows the hand completely.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); MountCameraOnPhone = file.Bind("Camera", "MountCameraOnPhone", true, "Bolts the camera to the handset itself, the way a real phone's front camera is bolted to its screen. This is what stops the phone ever getting in front of the lens - while walking, while looking around, and while holding the dial key, which raises the handset in front of the face. Turn it off for the old free-floating camera, which frames wider but can be blocked by the phone."); PhoneLensClearance = file.Bind("Camera", "PhoneLensClearance", 0.14f, new ConfigDescription("How far off the handset's screen face the lens sits, in metres, when MountCameraOnPhone is on. Must clear the phone's own geometry: too small and the camera ends up inside the model, which renders as a solid black picture.", (AcceptableValueBase)(object)new AcceptableValueRange(0.02f, 0.5f), Array.Empty())); SelfieFieldOfView = file.Bind("Camera", "SelfieFieldOfView", 82f, new ConfigDescription("Vertical field of view for this mod's cameras, in degrees. Wider than OpenBodyCams' own 65 on purpose: with the camera bolted to a handset held near the ear it sits about 30 cm from the face, and field of view is the only way to widen that shot - backing the camera off would just put the phone in front of it again. Raise it for more head and shoulders, lower it for a tighter face.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 120f), Array.Empty())); FrontDistance = file.Bind("Camera", "FrontDistance", 0.65f, new ConfigDescription("Front camera only: how far in front of the head the camera sits, in metres. Roughly arm's length. Larger values show more of their body and surroundings.", (AcceptableValueBase)(object)new AcceptableValueRange(0.15f, 3f), Array.Empty())); FrontHeightOffset = file.Bind("Camera", "FrontHeightOffset", 0.05f, new ConfigDescription("Front camera only: vertical offset from head height, in metres. Slightly positive reads as a phone held just above eye line.", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 1f), Array.Empty())); FrontLateralOffset = file.Bind("Camera", "FrontLateralOffset", -0.12f, new ConfigDescription("Front camera only: sideways offset in metres. Negative is towards their left hand, which is the hand LethalPhones puts the phone in - a small negative value is what makes the shot read as hand-held rather than a floating tripod.", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 1f), Array.Empty())); FrontYawAngle = file.Bind("Camera", "FrontYawAngle", -8f, new ConfigDescription("Front camera only: how far round to the side the camera sits, in degrees. Negative puts it on the subject's left, which is the hand LethalPhones puts the phone in - that is what makes the shot read as hand-held rather than as a webcam bolted to their forehead. Positive swings it round to the right instead.", (AcceptableValueBase)(object)new AcceptableValueRange(-80f, 80f), Array.Empty())); FrontPitchAngle = file.Bind("Camera", "FrontPitchAngle", -6f, new ConfigDescription("Front camera only: vertical angle in degrees. Negative sits below the eye line and looks slightly up, which is how a phone held at chest height sees you.", (AcceptableValueBase)(object)new AcceptableValueRange(-70f, 70f), Array.Empty())); RearForwardOffset = file.Bind("Camera", "RearForwardOffset", 0.15f, new ConfigDescription("Rear camera only: how far ahead of the head the camera sits, so their own body does not clip into frame.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); SubjectFraming = file.Bind("Camera", "SubjectFraming", 1f, new ConfigDescription("Framing multiplier for non-player callers - creatures holding a phone. The camera pulls back by a multiple of the subject's own measured size, so a knee-high lootbug and a man-sized masked enemy both frame sensibly from one number. Raise it to sit further away and show more of the room.", (AcceptableValueBase)(object)new AcceptableValueRange(0.2f, 5f), Array.Empty())); SwitchboardScreenOffset = file.Bind("Camera", "SwitchboardScreenOffset", 0f, new ConfigDescription("Extra distance in metres that the switchboard camera stands off from the centre of its screen. The mod already steps far enough out to avoid clipping into the mesh; raise this if the shot still starts inside the cabinet.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); EnableSwitchboardMonitor = file.Bind("Switchboard", "EnableSwitchboardMonitor", true, "Adds a knob to the ship's switchboard, between its select-next and select-previous knobs, that toggles a view of whoever is currently calling the switchboard on the switchboard's own display. Purely local: it sends no network messages, so nobody else in the lobby needs this mod, and each player toggles their own view."); FullscreenResolutionWidth = file.Bind("Switchboard", "FullscreenResolutionWidth", 0, new ConfigDescription("Width of the caller feed shown on the switchboard's display, or 0 to derive it from the framing (recommended). OpenBodyCams' field of view is VERTICAL, so a wider texture gains image at the sides rather than losing any at the top and bottom.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1920), Array.Empty())); FullscreenResolutionHeight = file.Bind("Switchboard", "FullscreenResolutionHeight", 480, new ConfigDescription("Height of the switchboard monitor feed.", (AcceptableValueBase)(object)new AcceptableValueRange(64, 1080), Array.Empty())); FullscreenFramerate = file.Bind("Switchboard", "FullscreenFramerate", 30f, new ConfigDescription("Frames per second for the switchboard monitor feed. 0 renders every frame.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); MonitorKnobSpacing = file.Bind("Switchboard", "MonitorKnobSpacing", 0f, new ConfigDescription("Where the new knob sits along the line joining the select-next and select-previous knobs. 0 is exactly halfway between them (the default); -1 and 1 sit on top of one or the other; values beyond that continue past them. The prefab's real positions live in an AssetBundle and are only measurable in game, so this is the dial to turn if it lands somewhere awkward.", (AcceptableValueBase)(object)new AcceptableValueRange(-3f, 3f), Array.Empty())); MonitorKnobScale = file.Bind("Switchboard", "MonitorKnobScale", 0.28f, new ConfigDescription("Diameter of the visible knob as a fraction of the gap between the switchboard's select-next and select-previous knobs. Sized against that gap rather than in metres so it stays right whatever scale the switchboard is placed at.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), Array.Empty())); MonitorKnobOutwardOffset = file.Bind("Switchboard", "MonitorKnobOutwardOffset", 0f, new ConfigDescription("Pushes the knob out of the panel along the switchboard's own forward axis, in metres. Raise this if the knob is buried in the cabinet - a knob whose collider is inside solid geometry cannot be interacted with, because the interact raycast hits the cabinet first.", (AcceptableValueBase)(object)new AcceptableValueRange(-0.5f, 0.5f), Array.Empty())); LogSwitchboardHierarchy = file.Bind("Switchboard", "LogSwitchboardHierarchy", true, "Dumps the switchboard's full child hierarchy to the log once - names, local positions, and which children have renderers or colliders. The switchboard prefab lives inside an AssetBundle, so this is the only way to see what is actually in there. Turn it on if the knob lands in the wrong place, and send the log. On by default for now, because the knob placement is still being dialled in; set it false once you are happy."); HidePhoneWidgetsDuringCall = file.Bind("Screen", "HidePhoneWidgetsDuringCall", true, "Take LethalPhones' own screen widgets - dialled number, call status, signal bars, volume icon - out of the picture entirely while video is playing, rather than just fading them. They are restored the moment the video stops, so the phone is still fully usable for dialling. Set false to leave them over the video."); HideFirstPersonPhoneFromCalls = file.Bind("Screen", "HideFirstPersonPhoneFromCalls", true, "Keeps your own held handset out of your outgoing picture, by removing its layer from this mod's cameras only. Every other camera - including your own eyes - still draws it normally. Turn it off if your phone stops rendering in your hands."); LetterboxPhoneVideo = file.Bind("Screen", "LetterboxPhoneVideo", true, "Fit the video inside the handset screen so the whole subject is visible, instead of filling the screen and cropping the sides off."); ScreenTint = file.Bind("Screen", "ScreenTint", new Color(0.42f, 1f, 0.55f), "The colour the feed is tinted towards, so it reads as something coming through a phone screen rather than a clean camera render. The default is a barely-there phosphor green."); ScreenTintStrength = file.Bind("Screen", "ScreenTintStrength", 0.45f, new ConfigDescription("How far the picture is pushed towards ScreenTint. 0 is a completely clean render, 1 is the full tint colour. Applies to the handset and the switchboard alike.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); ScreenEffectStrength = file.Bind("Screen", "ScreenEffectStrength", 0.18f, new ConfigDescription("Strength of the faint scanline overlay that sells the 'displayed on a screen' look. Deliberately low - it should be something you notice only if you look for it. 0 turns it off entirely.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); NightVisionStrength = file.Bind("Screen", "NightVisionStrength", 0.4f, new ConfigDescription("Scales OpenBodyCams' night-vision light on this mod's cameras only, never on the ship monitor. That light is cloned from the player's own night vision, which is green, and at full strength it is most of why a body cam feed looks so heavily tinted. Lower means a more natural, less washed-out picture; 1 matches the ship monitor exactly. Dark interiors will be genuinely darker as you lower it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); SwitchboardScreenInset = file.Bind("Switchboard", "SwitchboardScreenInset", 0f, new ConfigDescription("Padding in canvas units between the edge of the switchboard's display and the caller view drawn on it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 400f), Array.Empty())); RequireBodyCamShipUpgrade = file.Bind("Compatibility", "RequireBodyCamShipUpgrade", false, "OpenBodyCams can put the ship's body cam behind a purchasable upgrade (its own ShipUpgradeEnabled option, active whenever LethalLib is installed). When this is true, the phone video feed waits on that same purchase.\nDefault false, because a phone call is a different feature from the ship monitor, and silently showing nothing until someone buys an antenna makes the mod look broken. Set it true if you would rather the phone respect the upgrade."); ShowVideoForNonPlayerCallers = file.Bind("Flavour", "ShowVideoForNonPlayerCallers", true, "Also show a feed when the other end of the call is not a player - the switchboard, or one of the creatures that can pick up a phone. Disable for players-only FaceTime."); ReleaseOrphanedRenderTextures = file.Bind("Advanced", "ReleaseOrphanedRenderTextures", true, "OpenBodyCams allocates a fresh RenderTexture every time a camera's settings change and does not release the old one. When this is enabled the mod releases the previous texture of its OWN camera (never anyone else's) to stop VRAM creeping up over a long session."); } } internal enum CameraFacing { Front, Rear } internal enum TeardownMode { DestroyImmediately, DisableThenDestroyWhenIdle, DisableOnly } public static class FaceTimeEvents { public static event Action CallConnected; public static event Action CallEnded; public static event Action VideoTextureChanged; internal static void RaiseCallConnected(CallConnectedInfo info) { Raise(FaceTimeEvents.CallConnected, info, "CallConnected"); } internal static void RaiseCallEnded(PlayerPhone localPhone) { Raise(FaceTimeEvents.CallEnded, localPhone, "CallEnded"); } internal static void RaiseVideoTextureChanged(RenderTexture texture) { Raise(FaceTimeEvents.VideoTextureChanged, texture, "VideoTextureChanged"); } private static void Raise(Action handler, T argument, string name) { if (handler == null) { return; } Delegate[] invocationList = handler.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(argument); } catch (Exception ex) { Plugin.Log.LogError((object)("A " + name + " subscriber threw: " + ex)); } } } } public readonly struct CallConnectedInfo { public readonly PlayerPhone LocalPhone; public readonly PhoneBehavior RemotePhone; public readonly short RemoteNumber; public readonly bool RemoteIsPlayer; internal CallConnectedInfo(PlayerPhone localPhone, PhoneBehavior remotePhone, short remoteNumber, bool remoteIsPlayer) { LocalPhone = localPhone; RemotePhone = remotePhone; RemoteNumber = remoteNumber; RemoteIsPlayer = remoteIsPlayer; } } [BepInPlugin("rayan.LethalFaceTime", "LethalFaceTime", "1.0.0")] [BepInDependency("LethalPhones", "1.3.0")] [BepInDependency("Zaggy1024.OpenBodyCams", "3.0.0")] public sealed class Plugin : BaseUnityPlugin { private Harmony _harmony; private bool _loggedTickFailure; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static FaceTimeConfig Settings { get; private set; } private void Awake() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; try { Settings = new FaceTimeConfig(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += HandleSettingChanged; _harmony = new Harmony("rayan.LethalFaceTime"); _harmony.PatchAll(typeof(Plugin).Assembly); FaceTimeManager.Create(Settings, (MonoBehaviour)(object)this); Log.LogInfo((object)"LethalFaceTime 1.0.0 loaded."); } catch (Exception ex) { Log.LogError((object)("Failed to initialise LethalFaceTime: " + ex)); } } private void Update() { try { FaceTimeManager.Instance?.Tick(); } catch (Exception ex) { if (!_loggedTickFailure) { _loggedTickFailure = true; Log.LogError((object)("LethalFaceTime's update loop threw; further occurrences suppressed: " + ex)); } } } private void LateUpdate() { try { FaceTimeManager.Instance?.LateTick(); } catch (Exception ex) { if (!_loggedTickFailure) { _loggedTickFailure = true; Log.LogError((object)("LethalFaceTime's late update threw; further occurrences suppressed: " + ex)); } } } private void OnDestroy() { try { FaceTimeManager.Instance?.Shutdown(); ((BaseUnityPlugin)this).Config.SettingChanged -= HandleSettingChanged; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { Log.LogWarning((object)("Failed to unpatch cleanly: " + ex.Message)); } } private static void HandleSettingChanged(object sender, SettingChangedEventArgs args) { FaceTimeManager.NotifySettingsChanged(); } internal static void LogVerbose(string message) { if (Settings != null && Settings.VerboseLogging.Value) { Log.LogInfo((object)message); } } } internal static class PluginMetadata { public const string Guid = "rayan.LethalFaceTime"; public const string Name = "LethalFaceTime"; public const string Version = "1.0.0"; } internal static class Dependencies { public const string LethalPhonesGuid = "LethalPhones"; public const string LethalPhonesMinimumVersion = "1.3.0"; public const string OpenBodyCamsGuid = "Zaggy1024.OpenBodyCams"; public const string OpenBodyCamsMinimumVersion = "3.0.0"; } } namespace LethalFaceTime.Runtime { internal sealed class FaceTimeFeed : IDisposable { private const string GeneratedMonitorMaterialName = "BodyCamMaterial"; private static readonly FieldInfo MonitorOnMaterialField = AccessTools.Field(typeof(BodyCamComponent), "MonitorOnMaterial"); private static readonly FieldInfo NightVisionLightField = AccessTools.Field(typeof(BodyCamComponent), "nightVisionLight"); private readonly FaceTimeConfig _config; private readonly MonoBehaviour _coroutineHost; private Vector2Int _resolution; private float _framerate; private float _baseNightVisionIntensity = -1f; private int _extraCullingLayers; private int _removedCullingLayers; private GameObject _host; private BodyCamComponent _bodyCam; private RenderTexture _texture; private bool _rendering = true; public bool IsDead { get { if (!((Object)(object)_bodyCam == (Object)null)) { return (Object)(object)_host == (Object)null; } return true; } } public RenderTexture Texture => _texture; public bool HasSignal { get { if (IsDead || (Object)(object)_texture == (Object)null || !_rendering) { return false; } try { return !_bodyCam.IsBlanked; } catch (Exception) { return false; } } } public Lens Lens { get { if (IsDead) { return new Lens(null); } try { return new Lens(_bodyCam.GetCamera()); } catch (Exception) { return new Lens(null); } } } public int CameraCullingMask { get { if (IsDead) { return 0; } try { Camera camera = _bodyCam.GetCamera(); return ((Object)(object)camera != (Object)null) ? camera.cullingMask : 0; } catch (Exception) { return 0; } } } public event Action TextureChanged; private FaceTimeFeed(FaceTimeConfig config, MonoBehaviour coroutineHost, Vector2Int resolution, float framerate) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) _config = config; _coroutineHost = coroutineHost; _resolution = resolution; _framerate = framerate; } public void AddCullingLayer(int layer) { if (IsDead || layer < 0 || layer > 31) { return; } _extraCullingLayers |= 1 << layer; try { Camera camera = _bodyCam.GetCamera(); if ((Object)(object)camera != (Object)null) { camera.cullingMask |= _extraCullingLayers; } } catch (Exception ex) { Plugin.LogVerbose("Could not extend the camera culling mask: " + ex.Message); } } public void RemoveCullingLayer(int layer) { if (IsDead || layer <= 0 || layer > 31) { return; } int num = 1 << layer; if ((_removedCullingLayers & num) != 0) { return; } _removedCullingLayers |= num; try { Camera camera = _bodyCam.GetCamera(); if ((Object)(object)camera != (Object)null) { camera.cullingMask &= ~_removedCullingLayers; } } catch (Exception ex) { Plugin.LogVerbose("Could not trim the camera culling mask: " + ex.Message); } } public static FaceTimeFeed TryCreate(FaceTimeConfig config, MonoBehaviour coroutineHost, Vector2Int resolution, float framerate) { //IL_0015: 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_002a: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if (!OpenBodyCamsBridge.IsReady()) { Plugin.LogVerbose("OpenBodyCams is not initialised yet; deferring camera creation."); return null; } FaceTimeFeed faceTimeFeed = new FaceTimeFeed(config, coroutineHost, resolution, framerate); GameObject val = null; try { val = new GameObject("LethalFaceTime.PhoneCamera"); BodyCamComponent val2 = BodyCam.CreateBodyCam(val, (Renderer)null, -1, (ManualCameraRenderer)null); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)"OpenBodyCams returned no body cam component."); Object.Destroy((Object)(object)val); return null; } if (Array.IndexOf(BodyCamComponent.GetAllBodyCams(), val2) < 0) { Plugin.Log.LogWarning((object)"OpenBodyCams rejected the phone camera - its per-game setup has not finished. Will retry."); Object.Destroy((Object)(object)val); return null; } faceTimeFeed._host = val; faceTimeFeed._bodyCam = val2; val2.OnRenderTextureCreated += faceTimeFeed.HandleRenderTextureCreated; faceTimeFeed.CaptureCurrentTexture(); val2.ForceEnableCamera = true; val2.Framerate = framerate; if (val2.Resolution != resolution) { val2.Resolution = resolution; } faceTimeFeed.ApplyNightVision(); faceTimeFeed.ApplyFieldOfView(); Plugin.LogVerbose("Created a camera at " + ((Vector2Int)(ref resolution)).x + "x" + ((Vector2Int)(ref resolution)).y + " @ " + framerate + "fps."); return faceTimeFeed; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to create the OpenBodyCams feed: " + ex)); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } faceTimeFeed._host = null; faceTimeFeed._bodyCam = null; return null; } } public bool SetTargetToPlayer(PlayerControllerB player, string description) { if (IsDead || (Object)(object)player == (Object)null) { return false; } return Retarget(delegate { _bodyCam.SetTargetToPlayer(player); }, description); } public bool SetTargetToTransform(Transform transform, string description) { if (IsDead || (Object)(object)transform == (Object)null) { return false; } return Retarget(delegate { _bodyCam.SetTargetToTransform(transform); }, description); } private bool Retarget(Action apply, string description) { try { apply(); _bodyCam.StartTargetTransition(); Plugin.LogVerbose("Phone camera now targeting " + description + "."); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to target the phone camera: " + ex)); return false; } } public void SetRendering(bool rendering) { if (IsDead || _rendering == rendering) { return; } _rendering = rendering; try { ((Behaviour)_bodyCam).enabled = rendering; Plugin.LogVerbose(rendering ? "Phone camera resumed." : "Phone camera paused."); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to toggle the phone camera: " + ex)); } } public void ClearTarget() { if (IsDead) { return; } try { _bodyCam.SetTargetToNone(); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to clear the phone camera target: " + ex)); } } public void ApplySettings(Vector2Int resolution, float framerate) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (IsDead) { return; } _resolution = resolution; _framerate = framerate; try { _bodyCam.Framerate = framerate; if (_bodyCam.Resolution != resolution) { _bodyCam.Resolution = resolution; } CaptureCurrentTexture(); ApplyNightVision(); ApplyFieldOfView(); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply camera settings: " + ex)); } } public void ApplyFieldOfView() { if (IsDead) { return; } float value = _config.SelfieFieldOfView.Value; if (value <= 1f) { return; } try { Camera camera = _bodyCam.GetCamera(); if ((Object)(object)camera != (Object)null && !Mathf.Approximately(camera.fieldOfView, value)) { camera.fieldOfView = value; } } catch (Exception ex) { Plugin.LogVerbose("Could not set the camera field of view: " + ex.Message); } } public void ApplyNightVision() { if (IsDead || NightVisionLightField == null) { return; } try { object? value = NightVisionLightField.GetValue(_bodyCam); Light val = (Light)((value is Light) ? value : null); if (val != null) { float num = Mathf.Clamp01(_config.NightVisionStrength.Value); if (_baseNightVisionIntensity < 0f) { _baseNightVisionIntensity = val.intensity; } val.intensity = _baseNightVisionIntensity * num; } } catch (Exception ex) { Plugin.LogVerbose("Could not adjust the night-vision light: " + ex.Message); } } private void CaptureCurrentTexture() { if (IsDead) { return; } try { Camera camera = _bodyCam.GetCamera(); if ((Object)(object)camera != (Object)null && (Object)(object)camera.targetTexture != (Object)null) { HandleRenderTextureCreated(camera.targetTexture); } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to read the camera's render texture: " + ex)); } } private void HandleRenderTextureCreated(RenderTexture texture) { if (!((Object)(object)texture == (Object)(object)_texture)) { RenderTexture texture2 = _texture; _texture = texture; try { this.TextureChanged?.Invoke(texture); } catch (Exception ex) { Plugin.Log.LogError((object)("A texture consumer threw: " + ex)); } ReleaseSupersededTexture(texture2, texture); } } private void ReleaseSupersededTexture(RenderTexture previous, RenderTexture current) { if (!_config.ReleaseOrphanedRenderTextures.Value || (Object)(object)previous == (Object)null || (Object)(object)previous == (Object)(object)current) { return; } try { Camera val = (IsDead ? null : _bodyCam.GetCamera()); if ((Object)(object)val != (Object)null && (Object)(object)val.targetTexture == (Object)(object)previous) { return; } } catch (Exception) { return; } ReleaseDeferred(previous); } private void ReleaseDeferred(RenderTexture texture) { if (!((Object)(object)texture == (Object)null)) { if ((Object)(object)_coroutineHost == (Object)null || !((Component)_coroutineHost).gameObject.activeInHierarchy) { DestroyTexture(texture); } else { _coroutineHost.StartCoroutine(ReleaseAfterFrames(texture, 2)); } } } private static IEnumerator ReleaseAfterFrames(RenderTexture texture, int frames) { for (int i = 0; i < frames; i++) { yield return null; } DestroyTexture(texture); } private void DestroyOwnedMonitorMaterial() { if ((Object)(object)_bodyCam == (Object)null || MonitorOnMaterialField == null) { return; } try { object? value = MonitorOnMaterialField.GetValue(_bodyCam); Material val = (Material)((value is Material) ? value : null); if ((Object)(object)val != (Object)null && ((Object)val).name == "BodyCamMaterial") { Object.Destroy((Object)(object)val); } } catch (Exception ex) { Plugin.LogVerbose("Could not free the body cam's generated material: " + ex.Message); } } private static void DestroyTexture(RenderTexture texture) { if ((Object)(object)texture == (Object)null) { return; } try { Object.Destroy((Object)(object)texture); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to free an orphaned render texture: " + ex.Message)); } } public void Dispose() { RenderTexture texture = _texture; _texture = null; if ((Object)(object)_bodyCam != (Object)null) { try { _bodyCam.OnRenderTextureCreated -= HandleRenderTextureCreated; _bodyCam.ForceEnableCamera = false; ((Behaviour)_bodyCam).enabled = false; _bodyCam.SetTargetToNone(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error while detaching from the phone camera: " + ex.Message)); } } _rendering = false; DestroyOwnedMonitorMaterial(); if ((Object)(object)_host != (Object)null) { try { Object.Destroy((Object)(object)_host); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Error while destroying the phone camera host: " + ex2.Message)); } } _host = null; _bodyCam = null; if (_config.ReleaseOrphanedRenderTextures.Value) { ReleaseDeferred(texture); } Plugin.LogVerbose("Phone camera destroyed."); } } internal sealed class FaceTimeManager { private const int NoTargetKey = 0; private static FaceTimeManager _instance; private FaceTimeConfig _config; private PhoneScreen _screen; private FaceTimeFeed _feed; private PlayerPhone _trackedPhone; private ulong _sessionIdentity; private int _targetKey; private bool _inCall; private float _reconcileTimer; private float _idleTimer; private bool _idleTeardownDone; private bool _settingsDirty; private string _lastTraceKey; private bool _loggedFirstTick; private CameraFacing _facing; private readonly PhoneCameraRig _rig; private readonly SwitchboardMonitor _switchboardMonitor; private readonly FacingSync _facingSync = new FacingSync(); private CallTarget _currentTarget; private int _infoTraceBudget = 6; private readonly HashSet _warnedKeys = new HashSet(); private readonly MonoBehaviour _host; internal static FaceTimeManager Instance => _instance; public CameraFacing SelfFacing { get; private set; } private FaceTimeManager(FaceTimeConfig config, MonoBehaviour host) { _config = config; _host = host; _screen = new PhoneScreen(config); _rig = new PhoneCameraRig(config); _switchboardMonitor = new SwitchboardMonitor(config, host); _facing = config.DefaultFacing.Value; } public static void Create(FaceTimeConfig config, MonoBehaviour host) { if (_instance == null) { _instance = new FaceTimeManager(config, host); Plugin.Log.LogInfo((object)"Manager created; tick is driven by the plugin object."); } } public static void NotifyPhoneStateChanged(PlayerPhone phone) { if (_instance != null && !((Object)(object)phone == (Object)null) && !((Object)(object)phone != (Object)(object)LethalPhonesBridge.GetLocalPhone())) { _instance.Reconcile(); } } public static void NotifySettingsChanged() { if (_instance != null) { _instance._settingsDirty = true; } } public void Shutdown() { EndCall("the manager is shutting down"); DestroyFeed(); _screen?.Detach(); _rig?.Dispose(); _switchboardMonitor?.Dispose(); _facingSync.Shutdown(); if (_instance == this) { _instance = null; } } public void Tick() { if (_config == null) { return; } if (!_loggedFirstTick) { _loggedFirstTick = true; Plugin.Log.LogInfo((object)("Manager ticking. Enabled=" + _config.Enabled.Value + " Verbose=" + _config.VerboseLogging.Value + " ReconcileInterval=" + _config.ReconcileInterval.Value + "s Teardown=" + _config.TeardownBehaviour.Value)); } if (!_config.Enabled.Value) { if (_inCall || _feed != null || _screen.IsAttached) { EndCall("the mod was disabled"); DestroyFeed(); _screen.Detach(); } return; } if (_settingsDirty) { _settingsDirty = false; ApplyChangedSettings(); } _reconcileTimer += Time.deltaTime; if (_reconcileTimer >= _config.ReconcileInterval.Value) { _reconcileTimer = 0f; Reconcile(); } if (_inCall) { PollFlipKey(); TickActiveCall(); } else { TickIdle(); } _facingSync.Tick(SelfFacing, Time.deltaTime); _switchboardMonitor.Tick(); } private void PollFlipKey() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return; } KeyControl val = current[_config.FlipCameraKey.Value]; if (val != null && ((ButtonControl)val).wasPressedThisFrame) { PlayerControllerB val2 = StartOfRound.Instance?.localPlayerController; if ((!((Object)(object)val2 != (Object)null) || (!val2.inTerminalMenu && !val2.isTypingChat)) && LethalPhonesBridge.IsPhoneOpen(_trackedPhone)) { FlipCamera(); } } } private void ApplyChangedSettings() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) _screen.ApplyLayout(); _feed?.ApplySettings(_config.Resolution, _config.Framerate.Value); if (_inCall && _feed != null && !_feed.IsDead) { _screen.SetTexture((Texture)(object)_feed.Texture); } if (!_inCall) { _idleTeardownDone = false; if (_config.TeardownBehaviour.Value == TeardownMode.DestroyImmediately) { DestroyFeed(); } } } private void Reconcile() { if (_config == null || !_config.Enabled.Value) { return; } PlayerPhone localPhone = LethalPhonesBridge.GetLocalPhone(); if ((Object)(object)localPhone == (Object)null) { Trace("waiting-for-phone", "No local phone yet (LethalPhones has not assigned one)."); EndCall("the local phone is gone"); return; } CallSnapshot call = LethalPhonesBridge.ReadCall((PhoneBehavior)(object)localPhone); if (!call.IsConnected) { Trace("idle", "Local phone found, no active call."); EndCall("the call ended"); return; } CallTarget target = LethalPhonesBridge.ResolveTarget(call.RemotePhone); if (target.IsValid && !target.IsPlayer && !_config.ShowVideoForNonPlayerCallers.Value) { EndCall("the far end is not a player and non-player video is disabled"); return; } Trace("connected", "Active call to " + target.Describe() + " (number " + call.RemoteNumber + ")."); if (!_inCall || _sessionIdentity != call.Identity || (Object)(object)_trackedPhone != (Object)(object)localPhone) { BeginCall(localPhone, call, target); } else { TryBringUpVideo(target); } } private void BeginCall(PlayerPhone phone, CallSnapshot call, CallTarget target) { if (_inCall) { RaiseCallEnded(); } _trackedPhone = phone; _sessionIdentity = call.Identity; _targetKey = 0; _inCall = true; _idleTimer = 0f; _idleTeardownDone = false; _facing = _config.DefaultFacing.Value; _rig.InvalidateMeasurement(); Plugin.Log.LogInfo((object)("FaceTime call connected to " + target.Describe() + ".")); FaceTimeEvents.RaiseCallConnected(new CallConnectedInfo(phone, call.RemotePhone, call.RemoteNumber, target.IsPlayer)); TryBringUpVideo(target); } private bool TryBringUpVideo(CallTarget target) { if (!_inCall || (Object)(object)_trackedPhone == (Object)null) { return false; } if (!target.IsValid) { Trace("unresolved-target", "Call connected but the far end could not be resolved yet."); return false; } if (_config.RequireBodyCamShipUpgrade.Value && !OpenBodyCamsBridge.BodyCamsAreUnlocked()) { WarnOnce("bodycam-locked", "The phone video feed is waiting on the ship's Bodycam upgrade, because Compatibility/RequireBodyCamShipUpgrade is enabled. Buy the upgrade, or set that option to false to decouple the phone from it."); return false; } if (!_screen.IsAttached && !_screen.TryAttach(_trackedPhone)) { Trace("no-screen", "Phone screen canvas not available yet; will retry."); return false; } if (!EnsureFeed()) { Trace("no-feed", "Could not create the OpenBodyCams camera yet; will retry."); return false; } if (!AimCamera(target)) { return false; } if (_config.HideFirstPersonPhoneFromCalls.Value) { int num = OpenBodyCamsBridge.ResolveOwnerOnlyLayer(_feed.CameraCullingMask); if (num >= 0) { _screen.HideHandsetFromCalls(num); } else { WarnOnce("owner-only-layer", "Found no layer that your own camera draws but body cams skip, so your held phone may appear in your outgoing picture. Set Camera/MountCameraOnPhone = false if that bothers you more than the wider framing helps."); } } _feed.SetRendering(ShouldRender()); _screen.SetTexture((Texture)(object)_feed.Texture); _screen.WarnIfVisibleToBodyCam(_feed.CameraCullingMask); Trace("video-up", "Video layer live. facing=" + _facing.ToString() + " texture=" + (((Object)(object)_feed.Texture != (Object)null) ? (((Texture)_feed.Texture).width + "x" + ((Texture)_feed.Texture).height) : "") + " signal=" + _feed.HasSignal + " rendering=" + ShouldRender()); return true; } private bool AimCamera(CallTarget target) { _currentTarget = target; if (target.IsPlayer) { _facing = _facingSync.GetFacing(target.Player); } int num = ComputeTargetKey(target, _facing); if (_facing == CameraFacing.Front || !target.IsPlayer) { _rig.RearFacing = _facing == CameraFacing.Rear; if (!_rig.Aim(target, _feed.Lens)) { Trace("no-anchor", "Could not frame a selfie shot for " + target.Describe() + " yet."); return false; } if (num == _targetKey) { return true; } if (!_feed.SetTargetToTransform(_rig.Anchor, "front camera on " + target.Describe())) { return false; } _targetKey = num; return true; } if (num == _targetKey) { return true; } if (target.IsPlayer) { if (!_feed.SetTargetToPlayer(target.Player, "rear camera on " + target.Describe())) { return false; } _targetKey = num; return true; } Trace("unaimed", "No aiming strategy for " + target.Describe() + "."); return false; } public void FlipCamera() { SelfFacing = ((SelfFacing == CameraFacing.Front) ? CameraFacing.Rear : CameraFacing.Front); Plugin.Log.LogInfo((object)("Your outgoing camera flipped to " + SelfFacing.ToString() + "; announcing it to the other players.")); } private void TickActiveCall() { if ((Object)(object)_trackedPhone == (Object)null) { EndCall("the tracked phone was destroyed"); } else if (_feed != null && !_feed.IsDead && _screen.IsAttached) { _feed.SetRendering(ShouldRender()); bool hasSignal = _feed.HasSignal; bool phoneOpen = LethalPhonesBridge.IsPhoneOpen(_trackedPhone); float interference = LethalPhonesBridge.GetInterference((PhoneBehavior)(object)_trackedPhone); _screen.UpdateAppearance(hasSignal, phoneOpen, interference); } } public void LateTick() { if (_config != null && _config.Enabled.Value) { _switchboardMonitor.LateTick(); if (_inCall && _currentTarget.IsValid && (_facing != CameraFacing.Rear || !_currentTarget.IsPlayer) && _feed != null && !_feed.IsDead) { _rig.Aim(_currentTarget, _feed.Lens); } } } private void TickIdle() { if (_feed != null && !_idleTeardownDone && _config.TeardownBehaviour.Value == TeardownMode.DisableThenDestroyWhenIdle) { _idleTimer += Time.deltaTime; if (!(_idleTimer < _config.IdleKeepAliveSeconds.Value)) { _idleTeardownDone = true; Plugin.LogVerbose("Idle keep-alive elapsed; destroying the phone camera."); DestroyFeed(); } } } private bool ShouldRender() { if (_config.PauseFeedWhenPhoneStowed.Value) { return LethalPhonesBridge.IsPhoneOpen(_trackedPhone); } return true; } private bool EnsureFeed() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (_feed != null && !_feed.IsDead) { return true; } if (_feed != null) { DestroyFeed(); } _feed = FaceTimeFeed.TryCreate(_config, _host, _config.Resolution, _config.Framerate.Value); if (_feed == null) { return false; } _feed.TextureChanged += HandleFeedTextureChanged; _targetKey = 0; return true; } private void HandleFeedTextureChanged(RenderTexture texture) { if (_inCall) { _screen.SetTexture((Texture)(object)texture); } FaceTimeEvents.RaiseVideoTextureChanged(texture); } private void EndCall(string reason) { if (!_inCall) { return; } Plugin.Log.LogInfo((object)("FaceTime call disconnected because " + reason + ".")); RaiseCallEnded(); _inCall = false; _sessionIdentity = 0uL; _targetKey = 0; _idleTimer = 0f; _idleTeardownDone = false; _trackedPhone = null; _currentTarget = CallTarget.None; _screen.SetWidgetsHidden(hidden: false); _screen.SetTexture(null); switch (_config.TeardownBehaviour.Value) { case TeardownMode.DestroyImmediately: DestroyFeed(); break; case TeardownMode.DisableThenDestroyWhenIdle: case TeardownMode.DisableOnly: if (_feed != null && !_feed.IsDead) { _feed.SetRendering(rendering: false); _feed.ClearTarget(); _targetKey = 0; } break; } } private void RaiseCallEnded() { FaceTimeEvents.RaiseCallEnded(_trackedPhone); } private void DestroyFeed() { if (_feed != null) { _feed.TextureChanged -= HandleFeedTextureChanged; _feed.Dispose(); _feed = null; _targetKey = 0; _screen.SetTexture(null); FaceTimeEvents.RaiseVideoTextureChanged(null); } } private void Trace(string stateKey, string message) { if (!(_lastTraceKey == stateKey)) { _lastTraceKey = stateKey; string text = "[" + stateKey + "] " + message; if (_infoTraceBudget > 0) { _infoTraceBudget--; Plugin.Log.LogInfo((object)text); } else { Plugin.LogVerbose(text); } } } private void WarnOnce(string key, string message) { if (!_warnedKeys.Contains(key)) { _warnedKeys.Add(key); Plugin.Log.LogWarning((object)message); } } private static int ComputeTargetKey(CallTarget target, CameraFacing facing) { if (!target.IsValid) { return 0; } int num = ((target.IsPlayer && (Object)(object)target.Player != (Object)null) ? ((Object)target.Player).GetInstanceID() : (((Object)(object)target.Root != (Object)null) ? ((Object)target.Root).GetInstanceID() : 0)); if (num == 0) { return 0; } return num ^ ((int)(facing + 1) * 1597463007); } } internal sealed class FacingSync { private const string MessageName = "LethalFaceTime.Facing"; private const float ResendInterval = 5f; private readonly Dictionary _facings = new Dictionary(); private NetworkManager _registeredWith; private CameraFacing _lastSent; private bool _everSent; private float _resendTimer; private bool _warnedSendFailed; public void Tick(CameraFacing selfFacing, float deltaTime) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { _registeredWith = null; _everSent = false; _facings.Clear(); return; } EnsureRegistered(singleton); _resendTimer += deltaTime; if (!_everSent || selfFacing != _lastSent || !(_resendTimer < 5f)) { _resendTimer = 0f; _lastSent = selfFacing; _everSent = true; Announce(singleton, selfFacing); } } public CameraFacing GetFacing(PlayerControllerB player) { if ((Object)(object)player == (Object)null) { return CameraFacing.Front; } if (!_facings.TryGetValue(player.actualClientId, out var value)) { return CameraFacing.Front; } return value; } private void EnsureRegistered(NetworkManager manager) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if ((Object)(object)_registeredWith == (Object)(object)manager) { return; } try { CustomMessagingManager customMessagingManager = manager.CustomMessagingManager; if (customMessagingManager != null) { customMessagingManager.RegisterNamedMessageHandler("LethalFaceTime.Facing", new HandleNamedMessageDelegate(HandleMessage)); _registeredWith = manager; Plugin.LogVerbose("Registered the camera-facing message handler."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not register the camera-facing handler: " + ex.Message)); } } private unsafe void Announce(NetworkManager manager, CameraFacing facing) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) try { if (manager.IsServer) { _facings[manager.LocalClientId] = facing; Broadcast(manager, manager.LocalClientId, facing); return; } FastBufferWriter val = Build(manager.LocalClientId, facing); try { manager.CustomMessagingManager.SendNamedMessage("LethalFaceTime.Facing", 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception ex) { if (!_warnedSendFailed) { _warnedSendFailed = true; Plugin.Log.LogWarning((object)("Could not send your camera facing to the other players, so flipping will only change what the switchboard shows: " + ex.Message)); } } } private unsafe void Broadcast(NetworkManager manager, ulong subject, CameraFacing facing) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter val = Build(subject, facing); try { manager.CustomMessagingManager.SendNamedMessageToAll("LethalFaceTime.Facing", val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private static FastBufferWriter Build(ulong subject, CameraFacing facing) { //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) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter result = default(FastBufferWriter); ((FastBufferWriter)(ref result))..ctor(9, (Allocator)2, -1); ((FastBufferWriter)(ref result)).WriteValueSafe(ref subject, default(ForPrimitives)); byte b = (byte)facing; ((FastBufferWriter)(ref result)).WriteValueSafe(ref b, default(ForPrimitives)); return result; } private void HandleMessage(ulong senderClientId, FastBufferReader reader) { //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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) try { ulong key = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe(ref key, default(ForPrimitives)); byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); CameraFacing cameraFacing = ((b == 1) ? CameraFacing.Rear : CameraFacing.Front); NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null)) { if (singleton.IsServer && senderClientId != singleton.LocalClientId) { _facings[senderClientId] = cameraFacing; Broadcast(singleton, senderClientId, cameraFacing); } else { _facings[key] = cameraFacing; } } } catch (Exception ex) { Plugin.LogVerbose("Malformed camera-facing message ignored: " + ex.Message); } } public void Shutdown() { if ((Object)(object)_registeredWith != (Object)null) { try { CustomMessagingManager customMessagingManager = _registeredWith.CustomMessagingManager; if (customMessagingManager != null) { customMessagingManager.UnregisterNamedMessageHandler("LethalFaceTime.Facing"); } } catch (Exception ex) { Plugin.LogVerbose("Error while unregistering the camera-facing handler: " + ex.Message); } } _registeredWith = null; _facings.Clear(); _everSent = false; } } internal readonly struct Lens { private const float FallbackVerticalFov = 65f; private const float FallbackNearClip = 0.01f; private const float FallbackAspect = 1.3333334f; public readonly float TanHalfVertical; public readonly float TanHalfHorizontal; public readonly float NearClip; public Lens(Camera camera) { float num = 65f; float num2 = 1.3333334f; float nearClip = 0.01f; if ((Object)(object)camera != (Object)null) { if (camera.fieldOfView > 1f) { num = camera.fieldOfView; } if (camera.aspect > 0.01f) { num2 = camera.aspect; } if (camera.nearClipPlane > 0f) { nearClip = camera.nearClipPlane; } } TanHalfVertical = Mathf.Tan(num * 0.5f * (MathF.PI / 180f)); TanHalfHorizontal = TanHalfVertical * num2; NearClip = nearClip; } public float DistanceToFit(Vector3 size, float fill) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) fill = Mathf.Clamp(fill, 0.05f, 1f); float num = Mathf.Max(size.y, 0.05f) * 0.5f; float num2 = Mathf.Max(Mathf.Max(size.x, size.z), 0.05f) * 0.5f; float num3 = num / (TanHalfVertical * fill); float num4 = num2 / (TanHalfHorizontal * fill); return Mathf.Max(num3, num4); } } internal sealed class LocalPlayerPose { private static readonly FieldInfo ServerLeftArmRigField = AccessTools.Field(typeof(PlayerPhone), "serverLeftArmRig"); private static int _privateLayer = -1; private PlayerPhone _posed; private GameObject _model; private int[] _originalLayers; private Transform[] _modelTransforms; private bool _warnedNoRig; private bool _warnedNoLayer; private bool _loggedApplied; public void Apply(PlayerPhone localPhone, bool shouldPose, FaceTimeFeed feed, bool showPhoneToFeed = true) { if (!shouldPose || (Object)(object)localPhone == (Object)null) { Release(); return; } try { if ((Object)(object)_posed != (Object)(object)localPhone) { Release(); _posed = localPhone; _model = localPhone.serverPhoneModel; HideFromNormalCameras(showPhoneToFeed ? feed : null); PlayFlip(localPhone, "PhoneFlipOpen"); } localPhone.SetPhoneServerModelActive(true); SetArmWeight(localPhone, 1f); EnableRig(localPhone); if (!_loggedApplied) { _loggedApplied = true; Plugin.Log.LogInfo((object)("Posing the local player's third-person body to hold its phone" + ((_privateLayer >= 0) ? (" (on private layer " + _privateLayer + ")") : "") + ".")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not pose the local player's phone hand: " + ex.Message)); } } public void Release() { if ((Object)(object)_posed == (Object)null) { _model = null; return; } try { RestoreLayers(); SetArmWeight(_posed, 0f); PlayFlip(_posed, "PhoneFlipClosed"); _posed.SetPhoneServerModelActive(false); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not restore the local player's phone hand: " + ex.Message)); } _posed = null; _model = null; _modelTransforms = null; _originalLayers = null; } private void HideFromNormalCameras(FaceTimeFeed feed) { if ((Object)(object)_model == (Object)null) { return; } int num = ResolvePrivateLayer(); if (num < 0) { if (!_warnedNoLayer) { _warnedNoLayer = true; Plugin.Log.LogWarning((object)"Found no spare Unity layer, so the third-person phone will be visible in your own view as well as in the call."); } return; } _modelTransforms = _model.GetComponentsInChildren(true); _originalLayers = new int[_modelTransforms.Length]; for (int i = 0; i < _modelTransforms.Length; i++) { _originalLayers[i] = ((Component)_modelTransforms[i]).gameObject.layer; ((Component)_modelTransforms[i]).gameObject.layer = num; } feed?.AddCullingLayer(num); } private void RestoreLayers() { if (_modelTransforms == null || _originalLayers == null) { return; } for (int i = 0; i < _modelTransforms.Length; i++) { if ((Object)(object)_modelTransforms[i] != (Object)null) { ((Component)_modelTransforms[i]).gameObject.layer = _originalLayers[i]; } } } private static int ResolvePrivateLayer() { if (_privateLayer != -1) { if (_privateLayer != -2) { return _privateLayer; } return -1; } _privateLayer = -2; PlayerControllerB val = StartOfRound.Instance?.localPlayerController; Camera val2 = (((Object)(object)val != (Object)null) ? val.gameplayCamera : null); if ((Object)(object)val2 == (Object)null) { _privateLayer = -1; return -1; } int cullingMask = val2.cullingMask; for (int num = 31; num >= 8; num--) { if ((cullingMask & (1 << num)) == 0 && string.IsNullOrEmpty(LayerMask.LayerToName(num))) { _privateLayer = num; break; } } if (_privateLayer != -2) { return _privateLayer; } return -1; } private void EnableRig(PlayerPhone phone) { PlayerControllerB player = phone.player; if ((Object)(object)player == (Object)null || (Object)(object)player.meshContainer == (Object)null) { return; } Transform val = player.meshContainer.Find("metarig/Rig 1"); if (!((Object)(object)val == (Object)null)) { Rig component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && component.weight < 1f) { component.weight = 1f; } Animator playerBodyAnimator = player.playerBodyAnimator; RigBuilder val2 = (((Object)(object)playerBodyAnimator != (Object)null) ? ((Component)playerBodyAnimator).GetComponent() : null); if ((Object)(object)val2 != (Object)null && !((Behaviour)val2).enabled) { ((Behaviour)val2).enabled = true; } } } private static void PlayFlip(PlayerPhone phone, string clip) { GameObject serverPhoneModel = phone.serverPhoneModel; if (!((Object)(object)serverPhoneModel == (Object)null)) { Animator component = serverPhoneModel.GetComponent(); if ((Object)(object)component != (Object)null) { component.Play(clip); } } } private void SetArmWeight(PlayerPhone phone, float weight) { if (ServerLeftArmRigField == null) { if (!_warnedNoRig) { _warnedNoRig = true; Plugin.Log.LogWarning((object)"Could not find PlayerPhone.serverLeftArmRig, so the local player's arm will not be posed and the phone may look like it is floating in your own feed."); } return; } object? value = ServerLeftArmRigField.GetValue(phone); ChainIKConstraint val = (ChainIKConstraint)((value is ChainIKConstraint) ? value : null); if (val != null) { ((RigConstraint>)(object)val).weight = weight; } } } internal sealed class NetworkedFlag { private const float ResendInterval = 5f; private readonly string _messageName; private NetworkManager _registeredWith; private float _resendTimer; private bool _warnedSendFailed; public bool Value { get; private set; } public event Action Changed; public NetworkedFlag(string messageName) { _messageName = messageName; } public void Tick(float deltaTime) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { _registeredWith = null; return; } EnsureRegistered(singleton); if (singleton.IsServer) { _resendTimer += deltaTime; if (!(_resendTimer < 5f)) { _resendTimer = 0f; TryBroadcast(singleton, Value); } } } public unsafe void Request(bool value) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) Apply(value); NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { return; } try { if (singleton.IsServer) { TryBroadcast(singleton, value); return; } FastBufferWriter val = Build(value); try { singleton.CustomMessagingManager.SendNamedMessage(_messageName, 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception ex) { if (!_warnedSendFailed) { _warnedSendFailed = true; Plugin.Log.LogWarning((object)("Could not share the switchboard view with the other players, so it will only change on your screen: " + ex.Message)); } } } private void Apply(bool value) { if (Value == value) { return; } Value = value; try { this.Changed?.Invoke(value); } catch (Exception ex) { Plugin.Log.LogError((object)("A shared-flag subscriber threw: " + ex)); } } private void EnsureRegistered(NetworkManager manager) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown if ((Object)(object)_registeredWith == (Object)(object)manager) { return; } try { CustomMessagingManager customMessagingManager = manager.CustomMessagingManager; if (customMessagingManager != null) { customMessagingManager.RegisterNamedMessageHandler(_messageName, new HandleNamedMessageDelegate(HandleMessage)); _registeredWith = manager; Plugin.LogVerbose("Registered the shared-flag handler for " + _messageName + "."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not register " + _messageName + ": " + ex.Message)); } } private unsafe void TryBroadcast(NetworkManager manager, bool value) { //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) try { FastBufferWriter val = Build(value); try { manager.CustomMessagingManager.SendNamedMessageToAll(_messageName, val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception ex) { Plugin.LogVerbose("Could not broadcast " + _messageName + ": " + ex.Message); } } private static FastBufferWriter Build(bool value) { //IL_0016: 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_0022: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter result = default(FastBufferWriter); ((FastBufferWriter)(ref result))..ctor(1, (Allocator)2, -1); byte b = (value ? ((byte)1) : ((byte)0)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref b, default(ForPrimitives)); return result; } private void HandleMessage(ulong senderClientId, FastBufferReader reader) { //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) try { byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); bool value = b != 0; NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null)) { Apply(value); if (singleton.IsServer && senderClientId != singleton.LocalClientId) { TryBroadcast(singleton, value); } } } catch (Exception ex) { Plugin.LogVerbose("Malformed " + _messageName + " message ignored: " + ex.Message); } } public void Shutdown() { if ((Object)(object)_registeredWith != (Object)null) { try { CustomMessagingManager customMessagingManager = _registeredWith.CustomMessagingManager; if (customMessagingManager != null) { customMessagingManager.UnregisterNamedMessageHandler(_messageName); } } catch (Exception ex) { Plugin.LogVerbose("Error unregistering " + _messageName + ": " + ex.Message); } } _registeredWith = null; } } internal sealed class PhoneCameraRig : IDisposable { private const string AnchorName = "LethalFaceTime.CameraAnchor"; private const float SubjectFrameFill = 0.6f; private const float ClipEscape = 0.12f; private const float CollisionRadius = 0.15f; private const float MinimumDistance = 0.3f; private readonly FaceTimeConfig _config; private GameObject _anchorObject; private Transform _anchor; private Transform _measuredFor; private Bounds _localBounds; private bool _hasMeasurement; private Transform _mountedTo; private Vector3 _mountLocalDirection; private Vector3 _mountLocalUp; public bool FullBody { get; set; } public bool RearFacing { get; set; } public Transform Anchor { get { if (!((Object)(object)_anchor == (Object)null)) { return _anchor; } return null; } } public bool IsMounted => (Object)(object)_mountedTo != (Object)null; public PhoneCameraRig(FaceTimeConfig config) { _config = config; } public bool Aim(CallTarget target, Lens lens) { if (!target.IsValid) { return false; } Transform val = EnsureAnchor(); if ((Object)(object)val == (Object)null) { return false; } try { switch (target.Kind) { case CallTargetKind.Player: if (_config.MountCameraOnPhone.Value && (Object)(object)target.PhoneModel != (Object)null && AimMountedOnPhone(val, target)) { return true; } return AimAtPlayer(val, target); case CallTargetKind.Switchboard: return AimFromSwitchboardScreen(val, target, lens); default: return AimAtSubject(val, target, lens); } } catch (Exception ex) { Plugin.LogVerbose("Could not aim the phone camera: " + ex.Message); return false; } } private bool AimMountedOnPhone(Transform anchor, CallTarget target) { //IL_0031: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) Transform phoneModel = target.PhoneModel; Transform focus = target.Focus; if ((Object)(object)phoneModel == (Object)null || (Object)(object)focus == (Object)null) { return false; } if ((Object)(object)_mountedTo != (Object)(object)phoneModel) { Vector3 val = focus.position - phoneModel.position; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { return false; } _mountLocalDirection = phoneModel.InverseTransformDirection(((Vector3)(ref val)).normalized); _mountLocalUp = phoneModel.InverseTransformDirection(Vector3.up); _mountedTo = phoneModel; anchor.SetParent(phoneModel, false); Plugin.LogVerbose("Mounted the phone camera on '" + ((Object)phoneModel).name + "'."); } Vector3 val2 = (RearFacing ? (-_mountLocalDirection) : _mountLocalDirection); anchor.localPosition = val2 * _config.PhoneLensClearance.Value; Vector3 position = focus.position; Vector3 position2 = anchor.position; Vector3 val3 = (RearFacing ? (position2 - position) : (position - position2)); if (((Vector3)(ref val3)).sqrMagnitude < 1E-06f) { return false; } anchor.rotation = Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up); return true; } private void Unmount(Transform anchor) { if (!((Object)(object)_mountedTo == (Object)null)) { _mountedTo = null; if ((Object)(object)anchor != (Object)null) { anchor.SetParent((Transform)null, false); } } } private bool AimAtPlayer(Transform anchor, CallTarget target) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0146: 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_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: 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_0183: 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_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: 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_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) Unmount(anchor); PlayerControllerB player = target.Player; if ((Object)(object)player == (Object)null) { return false; } Transform focus = target.Focus; if ((Object)(object)focus == (Object)null) { return false; } Transform root = target.Root; if ((Object)(object)root == (Object)null) { return false; } Vector3 position = focus.position; Vector3 val = root.forward; Camera gameplayCamera = player.gameplayCamera; if ((Object)(object)gameplayCamera != (Object)null) { Vector3 forward = ((Component)gameplayCamera).transform.forward; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f) { val = ((Vector3)(ref forward)).normalized; } } Vector3 val2 = val; if ((Object)(object)target.PhoneModel != (Object)null) { Vector3 val3 = target.PhoneModel.position - position; if (((Vector3)(ref val3)).sqrMagnitude > 0.0025f) { float num = Mathf.Clamp01(_config.PhoneFollowStrength.Value); Vector3 val4 = Vector3.Slerp(val, ((Vector3)(ref val3)).normalized, num); if (((Vector3)(ref val4)).sqrMagnitude > 0.0001f) { val2 = ((Vector3)(ref val4)).normalized; } } } Vector3 val5 = Vector3.Cross(Vector3.up, val2); if (((Vector3)(ref val5)).sqrMagnitude < 0.0001f) { val5 = root.right; } Vector3 val6 = Quaternion.AngleAxis(_config.FrontYawAngle.Value, Vector3.up) * Quaternion.AngleAxis(_config.FrontPitchAngle.Value, ((Vector3)(ref val5)).normalized) * val2; val2 = ((Vector3)(ref val6)).normalized; float num2 = Mathf.Max(_config.FrontDistance.Value, 0.3f); Vector3 desired = position + val2 * num2 + Vector3.up * _config.FrontHeightOffset.Value; return Point(anchor, PullInsideGeometry(position, desired), position, Vector3.up); } private bool AimAtSubject(Transform anchor, CallTarget target, Lens lens) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) Unmount(anchor); Transform root = target.Root; if ((Object)(object)root == (Object)null) { return false; } Vector3 size = default(Vector3); Vector3 val2 = default(Vector3); if (target.Kind == CallTargetKind.Player) { Vector3 val = (((Object)(object)target.Focus != (Object)null) ? target.Focus.position : root.position); ((Vector3)(ref size))..ctor(0.7f, 2f, 0.7f); ((Vector3)(ref val2))..ctor(val.x, val.y - 0.75f, val.z); } else { Bounds val3 = Measure(root); val2 = root.TransformPoint(((Bounds)(ref val3)).center); size = Vector3.Scale(((Bounds)(ref val3)).size, AbsoluteScale(root)); } Vector3 val4 = ((target.Kind == CallTargetKind.Player) ? val2 : (((Object)(object)target.Focus != (Object)null) ? target.Focus.position : val2)); float fill = Mathf.Clamp01(0.6f / Mathf.Max(_config.SubjectFraming.Value, 0.1f)); float num = lens.DistanceToFit(size, fill); float num2 = Mathf.Max(((Vector3)(ref size)).magnitude * 0.5f, 0.25f); Vector3 position = val4 + root.forward * num + root.up * (_config.FrontHeightOffset.Value * num2) + root.right * (_config.FrontLateralOffset.Value * num2); return Point(anchor, position, val4, Vector3.up); } private bool AimFromSwitchboardScreen(Transform anchor, CallTarget target, Lens lens) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0102: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) Unmount(anchor); Transform root = target.Root; if ((Object)(object)root == (Object)null) { return false; } Transform val = (((Object)(object)target.Focus != (Object)null) ? target.Focus : root); Bounds val2 = Measure(val); Vector3 val3 = ((((Bounds)(ref val2)).size == Vector3.zero) ? val.position : val.TransformPoint(((Bounds)(ref val2)).center)); Vector3 val4 = root.forward; PlayerControllerB switchboardOperator = LethalPhonesBridge.GetSwitchboardOperator(); if ((Object)(object)switchboardOperator != (Object)null) { Vector3 val5 = ((Component)switchboardOperator).transform.position - val3; val5.y = 0f; if (((Vector3)(ref val5)).sqrMagnitude > 0.04f) { val4 = ((Vector3)(ref val5)).normalized; } } if (RearFacing) { val4 = -val4; } float num = Mathf.Max(0.12f, lens.NearClip * 4f) + _config.SwitchboardScreenOffset.Value; Vector3 val6 = val3 + val4 * num; return Point(anchor, val6, val6 + val4, Vector3.up); } private Vector3 PullInsideGeometry(Vector3 from, Vector3 desired) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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) Vector3 val = desired - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.05f) { return desired; } Vector3 val2 = val / magnitude; StartOfRound instance = StartOfRound.Instance; int num = (((Object)(object)instance != (Object)null) ? instance.collidersAndRoomMaskAndDefault : (-1)); try { RaycastHit val3 = default(RaycastHit); if (Physics.SphereCast(from, 0.15f, val2, ref val3, magnitude, num, (QueryTriggerInteraction)1)) { float num2 = Mathf.Max(((RaycastHit)(ref val3)).distance - 0.075f, 0.3f); return from + val2 * Mathf.Min(num2, magnitude); } } catch (Exception ex) { Plugin.LogVerbose("Camera collision check failed: " + ex.Message); } return desired; } private static bool Point(Transform anchor, Vector3 position, Vector3 lookAt, Vector3 up) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) Vector3 val = lookAt - position; if (((Vector3)(ref val)).sqrMagnitude < 1E-06f) { return false; } anchor.SetPositionAndRotation(position, Quaternion.LookRotation(val, up)); return true; } private Bounds Measure(Transform root) { //IL_001f: 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_0017: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0101: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: 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_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (_hasMeasurement && (Object)(object)_measuredFor == (Object)(object)root) { return _localBounds; } Bounds val = default(Bounds); ((Bounds)(ref val))..ctor(Vector3.zero, Vector3.zero); bool flag = false; Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); Bounds val3 = default(Bounds); foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } switch (((object)val2).GetType().Name) { case "ParticleSystemRenderer": case "TrailRenderer": case "LineRenderer": continue; } Bounds bounds = val2.bounds; ((Bounds)(ref val3))..ctor(root.InverseTransformPoint(((Bounds)(ref bounds)).center), Vector3.Scale(((Bounds)(ref bounds)).size, Reciprocal(AbsoluteScale(root)))); if (flag) { ((Bounds)(ref val)).Encapsulate(val3); continue; } val = val3; flag = true; } _measuredFor = root; _localBounds = val; _hasMeasurement = true; Plugin.LogVerbose("Measured framing for " + ((Object)root).name + ": local size=" + ((object)((Bounds)(ref val)).size/*cast due to .constrained prefix*/).ToString()); return val; } private static Vector3 AbsoluteScale(Transform t) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Vector3 lossyScale = t.lossyScale; return new Vector3(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z)); } private static Vector3 Reciprocal(Vector3 v) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) return new Vector3(Mathf.Approximately(v.x, 0f) ? 1f : (1f / v.x), Mathf.Approximately(v.y, 0f) ? 1f : (1f / v.y), Mathf.Approximately(v.z, 0f) ? 1f : (1f / v.z)); } public void InvalidateMeasurement() { _hasMeasurement = false; _measuredFor = null; } private Transform EnsureAnchor() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if ((Object)(object)_anchor != (Object)null) { return _anchor; } try { _anchorObject = new GameObject("LethalFaceTime.CameraAnchor"); _anchor = _anchorObject.transform; return _anchor; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to create the camera anchor: " + ex)); _anchorObject = null; _anchor = null; return null; } } public void Dispose() { if ((Object)(object)_anchorObject != (Object)null) { try { Object.Destroy((Object)(object)_anchorObject); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error while destroying the camera anchor: " + ex.Message)); } } _anchorObject = null; _anchor = null; _mountedTo = null; InvalidateMeasurement(); } } internal sealed class PhoneScreen { private const string LayerName = "LethalFaceTime.Screen"; private readonly FaceTimeConfig _config; private readonly VideoLayer _layer = new VideoLayer(); private PlayerPhone _phone; private Canvas _canvas; private bool _warnedAboutCullingMask; private readonly Dictionary _movedHandsetObjects = new Dictionary(); private int _handsetHiddenLayer = -1; private readonly List _widgets = new List(); private readonly List _widgetsWereEnabled = new List(); private bool _widgetsHidden; public int HandsetLayer { get; private set; } = -1; public bool IsAttached { get { if (_layer.IsAttached) { return (Object)(object)_phone != (Object)null; } return false; } } public PhoneScreen(FaceTimeConfig config) { _config = config; } public bool TryAttach(PlayerPhone phone) { if ((Object)(object)phone == (Object)null) { return false; } if (IsAttached && (Object)(object)_phone == (Object)(object)phone) { return true; } Detach(); Canvas val = LethalPhonesBridge.FindLocalScreenCanvas(phone); if ((Object)(object)val == (Object)null) { Plugin.LogVerbose("The local phone's screen canvas is not available yet."); return false; } if (!_layer.TryAttach(val, "LethalFaceTime.Screen", _config.DrawBehindPhoneWidgets.Value)) { return false; } _phone = phone; _canvas = val; ApplyLayout(); ResolveHandsetLayer(); Plugin.LogVerbose("Attached the video layer to the phone screen."); return true; } public void ApplyLayout() { if (IsAttached) { _layer.SetInset(_config.ScreenInset.Value); } } public void SetTexture(Texture texture) { if (IsAttached) { _layer.SetTexture(texture); _layer.FitAspect(_config.LetterboxPhoneVideo.Value); } } public void UpdateAppearance(bool hasSignal, bool phoneOpen, float interference) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (IsAttached) { if (!(hasSignal && phoneOpen)) { _layer.SetTexture(null); SetWidgetsHidden(hidden: false); } else { _layer.SetLook(ScreenLook.Tint(_config, interference), _config.ScreenEffectStrength.Value); SetWidgetsHidden(_config.HidePhoneWidgetsDuringCall.Value); } } } public void SetWidgetsHidden(bool hidden) { if (!IsAttached || hidden == _widgetsHidden) { return; } EnsureWidgetsCaptured(); for (int i = 0; i < _widgets.Count; i++) { Graphic val = _widgets[i]; if (!((Object)(object)val == (Object)null)) { ((Behaviour)val).enabled = !hidden && _widgetsWereEnabled[i]; } } _widgetsHidden = hidden; } private void EnsureWidgetsCaptured() { if (_widgets.Count > 0) { return; } TextMeshProUGUI val = (((Object)(object)_phone != (Object)null) ? _phone.personalPhoneNumberUI : null); Graphic[] componentsInChildren = ((Component)_canvas).GetComponentsInChildren(true); foreach (Graphic val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)((Component)val2).transform.parent != (Object)(object)((Component)_canvas).transform) && !((Object)((Component)val2).gameObject).name.StartsWith("LethalFaceTime.", StringComparison.Ordinal) && (!((Object)(object)val != (Object)null) || !((Object)(object)val2 == (Object)(object)val))) { _widgets.Add(val2); _widgetsWereEnabled.Add(((Behaviour)val2).enabled); } } Plugin.LogVerbose("Captured " + _widgets.Count + " phone screen widgets."); } public void WarnIfVisibleToBodyCam(int cameraCullingMask) { if (!_warnedAboutCullingMask && IsAttached && cameraCullingMask != 0) { _warnedAboutCullingMask = true; int num = (((Object)(object)_layer.Rect != (Object)null) ? ((Component)_layer.Rect).gameObject.layer : 0); if ((cameraCullingMask & (1 << num)) != 0) { Plugin.Log.LogInfo((object)("The phone's video layer is on layer " + num + ", which the body cam also renders. This only matters if both call participants stand in the same spot, and would show as a screen-inside-a-screen. Set Screen/LayerOverride = 23 if you ever see it.")); } } } public void HideHandsetFromCalls(int ownerOnlyLayer) { if (!_config.HideFirstPersonPhoneFromCalls.Value || ownerOnlyLayer < 0 || !IsAttached) { return; } GameObject localPhoneModel = _phone.localPhoneModel; if ((Object)(object)localPhoneModel == (Object)null) { return; } if (_handsetHiddenLayer != ownerOnlyLayer) { RestoreHandsetLayers(); _handsetHiddenLayer = ownerOnlyLayer; Plugin.Log.LogInfo((object)("Hiding the local handset from calls on layer " + ownerOnlyLayer + " ('" + LayerMask.LayerToName(ownerOnlyLayer) + "').")); } Transform[] componentsInChildren = localPhoneModel.GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.layer != ownerOnlyLayer) { if (!_movedHandsetObjects.ContainsKey(val)) { _movedHandsetObjects[val] = ((Component)val).gameObject.layer; } ((Component)val).gameObject.layer = ownerOnlyLayer; } } } private void RestoreHandsetLayers() { foreach (KeyValuePair movedHandsetObject in _movedHandsetObjects) { if ((Object)(object)movedHandsetObject.Key != (Object)null) { ((Component)movedHandsetObject.Key).gameObject.layer = movedHandsetObject.Value; } } _movedHandsetObjects.Clear(); _handsetHiddenLayer = -1; } private void ResolveHandsetLayer() { HandsetLayer = -1; GameObject val = (((Object)(object)_phone != (Object)null) ? _phone.localPhoneModel : null); if ((Object)(object)val == (Object)null) { return; } Renderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Renderer val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { HandsetLayer = ((Component)val2).gameObject.layer; break; } } Plugin.Log.LogInfo((object)("Local handset renders on layer " + HandsetLayer + " ('" + LayerMask.LayerToName(HandsetLayer) + "').")); } public void Detach() { RestoreHandsetLayers(); HandsetLayer = -1; SetWidgetsHidden(hidden: false); _widgets.Clear(); _widgetsWereEnabled.Clear(); _widgetsHidden = false; _layer.Detach(); _canvas = null; _phone = null; _warnedAboutCullingMask = false; } } internal static class ScreenLook { public static Color Tint(FaceTimeConfig config, float interference) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_007d: 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) float num = Mathf.Clamp01(config.ScreenTintStrength.Value); Color val = Color.Lerp(Color.white, config.ScreenTint.Value, num); float num2 = Mathf.Clamp01(config.ScreenOpacity.Value); if (config.InterferenceDegradesVideo.Value) { num2 *= Mathf.Lerp(1f, 0.12f, Mathf.Clamp01(interference)); } num2 = Mathf.Round(num2 * 32f) / 32f; return new Color(val.r, val.g, val.b, num2); } } internal sealed class SwitchboardMonitor : IDisposable { private const string KnobName = "LethalFaceTime.MonitorKnob"; private const string SourceKnobName = "UpCube"; private const string NeighbourKnobName = "DownCube"; private const string VisibleKnobName = "LethalFaceTime.MonitorKnobMesh"; private const string CabinetMeshName = "SwitchboardMesh"; private readonly FaceTimeConfig _config; private readonly MonoBehaviour _host; private readonly PhoneCameraRig _rig; private readonly SwitchboardScreenSurface _surface; private readonly LocalPlayerPose _localPose = new LocalPlayerPose(); private SwitchboardPhone _switchboard; private GameObject _knob; private FaceTimeFeed _feed; private CallTarget _target; private int _targetKey; private readonly NetworkedFlag _viewShared = new NetworkedFlag("LethalFaceTime.SwitchboardView"); private bool _active; private bool _warnedNoSource; private bool _dumpedHierarchy; private CameraFacing _selfFacing; public SwitchboardMonitor(FaceTimeConfig config, MonoBehaviour host) { _config = config; _host = host; _rig = new PhoneCameraRig(config); _surface = new SwitchboardScreenSurface(config); } public void Tick() { if (!_config.EnableSwitchboardMonitor.Value) { if (_active) { Deactivate("the switchboard monitor was disabled"); } return; } _viewShared.Tick(Time.deltaTime); EnsureKnob(); if (_viewShared.Value != _active) { if (_viewShared.Value) { _active = true; _targetKey = 0; Plugin.Log.LogInfo((object)"Switchboard caller view switched on."); } else { Deactivate("the view was switched off"); } } if (!_active) { return; } if ((Object)(object)_switchboard == (Object)null) { Deactivate("the switchboard is gone"); } else { if (!_surface.TryAttach(_switchboard)) { return; } CallSnapshot callSnapshot = LethalPhonesBridge.ReadCall((PhoneBehavior)(object)_switchboard); if (!callSnapshot.IsConnected) { _surface.SetTexture(null); _localPose.Release(); ReleaseCamera(); return; } PhoneBehavior remotePhone = callSnapshot.RemotePhone; PlayerPhone val = (PlayerPhone)(object)((remotePhone is PlayerPhone) ? remotePhone : null); if (val != null && !val.toggled) { _surface.SetTexture(null); _localPose.Release(); if (_feed != null && !_feed.IsDead) { _feed.SetRendering(rendering: false); } return; } CallTarget target = LethalPhonesBridge.ResolveTarget(callSnapshot.RemotePhone); if (!target.IsValid || !EnsureCamera()) { _surface.SetTexture(null); return; } _target = target; bool flag = IsLocalPlayer(target); _selfFacing = ((flag && FaceTimeManager.Instance != null) ? FaceTimeManager.Instance.SelfFacing : CameraFacing.Front); bool flag2 = flag && _selfFacing == CameraFacing.Rear; _rig.RearFacing = flag2; int num = (((Object)(object)target.Root != (Object)null) ? ((Object)target.Root).GetInstanceID() : 0); if (num != 0 && flag2) { num ^= 0x5F3759DF; } if (num != _targetKey) { _rig.InvalidateMeasurement(); if (_rig.Aim(target, _feed.Lens) && _feed.SetTargetToTransform(_rig.Anchor, "switchboard " + _selfFacing.ToString() + " cam on " + target.Describe())) { _targetKey = num; Plugin.Log.LogInfo((object)("Switchboard monitor now showing " + target.Describe() + " (" + _selfFacing.ToString() + " camera).")); } } _localPose.Apply(LethalPhonesBridge.GetLocalPhone(), flag, _feed, !_rig.IsMounted); if (_config.HideFirstPersonPhoneFromCalls.Value) { int localHandsetLayer = LethalPhonesBridge.GetLocalHandsetLayer(); if (localHandsetLayer > 0) { _feed.RemoveCullingLayer(localHandsetLayer); } } _feed.SetRendering(rendering: true); _surface.SetTexture((Texture)(object)(_feed.HasSignal ? _feed.Texture : null)); _surface.UpdateAppearance(_feed.HasSignal, LethalPhonesBridge.GetInterference((PhoneBehavior)(object)_switchboard)); } } private static bool IsLocalPlayer(CallTarget target) { if (!target.IsPlayer || (Object)(object)target.Player == (Object)null) { return false; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { return (Object)(object)target.Player == (Object)(object)instance.localPlayerController; } return false; } public void LateTick() { if (_active && _feed != null && !_feed.IsDead && _target.IsValid) { _rig.Aim(_target, _feed.Lens); } } private void Toggle() { _viewShared.Request(!_viewShared.Value); } private void Deactivate(string reason) { _active = false; _target = CallTarget.None; _targetKey = 0; _surface.SetTexture(null); _surface.Detach(); _localPose.Release(); ReleaseCamera(); Plugin.LogVerbose("Switchboard caller view off: " + reason + "."); } private bool EnsureCamera() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (_feed != null && !_feed.IsDead) { return true; } if (_feed != null) { ReleaseCamera(); } _feed = FaceTimeFeed.TryCreate(_config, _host, _config.FullscreenResolution, _config.FullscreenFramerate.Value); return _feed != null; } private void ReleaseCamera() { if (_feed != null) { _feed.Dispose(); _feed = null; _targetKey = 0; } } private void EnsureKnob() { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: 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_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_knob != (Object)null && (Object)(object)_switchboard != (Object)null) { return; } PhoneNetworkHandler instance = PhoneNetworkHandler.Instance; _switchboard = (((Object)(object)instance != (Object)null) ? instance.switchboard : null); if ((Object)(object)_switchboard == (Object)null) { return; } Transform transform = ((Component)_switchboard).transform; Transform val = transform.Find("UpCube"); if ((Object)(object)val == (Object)null) { if (!_warnedNoSource) { _warnedNoSource = true; Plugin.Log.LogWarning((object)"Could not find the switchboard's 'UpCube' knob to clone, so the monitor knob was not added. LethalPhones may have renamed it."); } return; } Transform val2 = transform.Find("LethalFaceTime.MonitorKnob"); if ((Object)(object)val2 != (Object)null) { _knob = ((Component)val2).gameObject; return; } try { _knob = Object.Instantiate(((Component)val).gameObject, transform); ((Object)_knob).name = "LethalFaceTime.MonitorKnob"; _knob.transform.localPosition = NextKnobPosition(transform, val); _knob.transform.localRotation = val.localRotation; _knob.transform.localScale = val.localScale; InteractTrigger component = _knob.GetComponent(); if ((Object)(object)component == (Object)null) { Plugin.Log.LogWarning((object)"The cloned knob has no InteractTrigger; removing it again."); Object.Destroy((Object)(object)_knob); _knob = null; return; } ((UnityEventBase)component.onInteract).RemoveAllListeners(); ((UnityEventBase)component.onInteractEarly).RemoveAllListeners(); ((UnityEventBase)component.onStopInteract).RemoveAllListeners(); component.hoverTip = "Toggle caller view : [LMB]"; component.disabledHoverTip = "Toggle caller view"; component.interactable = true; component.holdInteraction = false; component.disableTriggerMesh = false; Transform val3 = transform.Find("DownCube"); float gap = (((Object)(object)val3 != (Object)null) ? Vector3.Distance(val.localPosition, val3.localPosition) : 0.4f); AddVisibleKnob(transform, _knob.transform.localPosition, gap); DumpHierarchyOnce(transform); ((UnityEvent)(object)component.onInteract).AddListener((UnityAction)delegate { Toggle(); }); Plugin.Log.LogInfo((object)("Added the switchboard caller-view knob at localPos=" + ((object)_knob.transform.localPosition/*cast due to .constrained prefix*/).ToString() + " (source 'UpCube' at " + ((object)val.localPosition/*cast due to .constrained prefix*/).ToString() + ", neighbour at " + (((Object)(object)transform.Find("DownCube") != (Object)null) ? ((object)transform.Find("DownCube").localPosition/*cast due to .constrained prefix*/).ToString() : "") + ").")); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to add the switchboard monitor knob: " + ex)); if ((Object)(object)_knob != (Object)null) { Object.Destroy((Object)(object)_knob); } _knob = null; } } private Vector3 NextKnobPosition(Transform parent, Transform source) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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_008a: 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) Transform val = parent.Find("DownCube"); if ((Object)(object)val == (Object)null) { return source.localPosition; } Vector3 val2 = (source.localPosition + val.localPosition) * 0.5f; Vector3 val3 = (source.localPosition - val.localPosition) * 0.5f; Vector3 val4 = Vector3.forward * _config.MonitorKnobOutwardOffset.Value; return val2 + val3 * _config.MonitorKnobSpacing.Value + val4; } private void AddVisibleKnob(Transform switchboardRoot, Vector3 knobLocalPosition, float gap) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) try { Transform val = switchboardRoot.Find("LethalFaceTime.MonitorKnobMesh"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2); ((Object)obj).name = "LethalFaceTime.MonitorKnobMesh"; Collider component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } Transform transform = obj.transform; transform.SetParent(switchboardRoot, false); float num = Mathf.Max(gap * _config.MonitorKnobScale.Value, 0.01f); transform.localRotation = Quaternion.Euler(90f, 0f, 0f); transform.localScale = new Vector3(num, num * 0.18f, num); transform.localPosition = knobLocalPosition + new Vector3(0f, 0f, num * 0.25f); obj.layer = ((Component)switchboardRoot).gameObject.layer; MeshRenderer component2 = obj.GetComponent(); Transform val2 = switchboardRoot.Find("SwitchboardMesh"); MeshRenderer val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent() : null); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)switchboardRoot).GetComponentInChildren(true); } if ((Object)(object)component2 != (Object)null && (Object)(object)val3 != (Object)null) { ((Renderer)component2).sharedMaterial = ((Renderer)val3).sharedMaterial; } Plugin.Log.LogInfo((object)("Built the caller-view knob at " + ((object)transform.localPosition/*cast due to .constrained prefix*/).ToString() + " diameter=" + num + " (gap=" + gap + ").")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not build the visible knob: " + ex.Message)); } } private void DumpHierarchyOnce(Transform root) { if (!_dumpedHierarchy && _config.LogSwitchboardHierarchy.Value) { _dumpedHierarchy = true; Plugin.Log.LogInfo((object)"--- switchboard hierarchy ---"); Dump(root, 0); Plugin.Log.LogInfo((object)"--- end switchboard hierarchy ---"); } } private static void Dump(Transform t, int depth) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (depth <= 4) { string text = string.Empty; if ((Object)(object)((Component)t).GetComponent() != (Object)null) { text += " mesh"; } if ((Object)(object)((Component)t).GetComponent() != (Object)null) { text += " collider"; } if ((Object)(object)((Component)t).GetComponent() != (Object)null) { text += " trigger"; } if ((Object)(object)((Component)t).GetComponent() != (Object)null) { text += " canvas"; } Plugin.Log.LogInfo((object)(new string(' ', depth * 2) + ((Object)t).name + " localPos=" + ((object)t.localPosition/*cast due to .constrained prefix*/).ToString() + " tag=" + ((Component)t).tag + " layer=" + ((Component)t).gameObject.layer + text)); for (int i = 0; i < t.childCount; i++) { Dump(t.GetChild(i), depth + 1); } } } public void Dispose() { Deactivate("shutting down"); _viewShared.Shutdown(); _rig.Dispose(); if ((Object)(object)_knob != (Object)null) { try { Object.Destroy((Object)(object)_knob); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error while removing the monitor knob: " + ex.Message)); } } _knob = null; _switchboard = null; } } internal sealed class SwitchboardScreenSurface { private const string LayerName = "LethalFaceTime.SwitchboardScreen"; private readonly FaceTimeConfig _config; private readonly VideoLayer _layer = new VideoLayer(); private SwitchboardPhone _switchboard; private Canvas _canvas; private bool _warnedNoCanvas; public bool IsAttached { get { if (_layer.IsAttached) { return (Object)(object)_switchboard != (Object)null; } return false; } } public SwitchboardScreenSurface(FaceTimeConfig config) { _config = config; } public bool TryAttach(SwitchboardPhone switchboard) { if ((Object)(object)switchboard == (Object)null) { return false; } if (IsAttached && (Object)(object)_switchboard == (Object)(object)switchboard) { return true; } Detach(); Canvas val = FindCanvas(switchboard); if ((Object)(object)val == (Object)null) { return false; } if (!_layer.TryAttach(val, "LethalFaceTime.SwitchboardScreen", drawFirst: false)) { return false; } _switchboard = switchboard; _canvas = val; _layer.SetInset(_config.SwitchboardScreenInset.Value); Plugin.Log.LogInfo((object)("Attached the caller view to the switchboard's screen ('" + ((Object)val).name + "').")); return true; } private Canvas FindCanvas(SwitchboardPhone switchboard) { Transform val = LethalPhonesBridge.FindSwitchboardScreen(switchboard); Canvas val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInChildren(true) : null); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)switchboard).GetComponentInChildren(true); } if ((Object)(object)val2 == (Object)null && !_warnedNoCanvas) { _warnedNoCanvas = true; Plugin.Log.LogWarning((object)"Found no Canvas on the switchboard, so the caller view cannot be drawn on its screen. LethalPhones may have changed the switchboard prefab."); } return val2; } public void SetTexture(Texture texture) { if (IsAttached) { _layer.SetTexture(texture); _layer.FitAspect(letterbox: true); } } public void UpdateAppearance(bool hasSignal, float interference) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (IsAttached) { if (!hasSignal) { _layer.SetTexture(null); } else { _layer.SetLook(ScreenLook.Tint(_config, interference), _config.ScreenEffectStrength.Value); } } } public void Detach() { _layer.Detach(); _canvas = null; _switchboard = null; } } internal sealed class VideoLayer { private const int ScanlineRows = 4; private static Texture2D _scanlines; private Canvas _canvas; private RawImage _backdrop; private RawImage _video; private RawImage _effect; private string _name; private Color _appliedTint = Color.clear; private float _appliedEffect = -1f; private float _inset; private bool _letterbox; public bool IsAttached { get { if ((Object)(object)_video != (Object)null) { return (Object)(object)_canvas != (Object)null; } return false; } } public RectTransform Rect { get { if (!((Object)(object)_video != (Object)null)) { return null; } return ((Graphic)_video).rectTransform; } } public bool TryAttach(Canvas canvas, string name, bool drawFirst) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)canvas == (Object)null) { return false; } if (IsAttached && (Object)(object)_canvas == (Object)(object)canvas && _name == name) { return true; } Detach(); try { _canvas = canvas; _name = name; _backdrop = BuildOrReuse(canvas, name + ".Backdrop"); _video = BuildOrReuse(canvas, name); _effect = BuildOrReuse(canvas, name + ".Scanlines"); _backdrop.texture = null; ((Graphic)_backdrop).color = Color.black; _effect.texture = (Texture)(object)GetScanlineTexture(); ((Graphic)_effect).color = Color.clear; Stretch(((Graphic)_backdrop).rectTransform); Stretch(((Graphic)_video).rectTransform); Stretch(((Graphic)_effect).rectTransform); if (drawFirst) { ((Transform)((Graphic)_backdrop).rectTransform).SetAsFirstSibling(); ((Transform)((Graphic)_video).rectTransform).SetSiblingIndex(1); ((Transform)((Graphic)_effect).rectTransform).SetSiblingIndex(2); } else { ((Transform)((Graphic)_backdrop).rectTransform).SetAsLastSibling(); ((Transform)((Graphic)_video).rectTransform).SetAsLastSibling(); ((Transform)((Graphic)_effect).rectTransform).SetAsLastSibling(); } SetTexture(null); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to build the video layer '" + name + "': " + ex)); Detach(); return false; } } private static RawImage BuildOrReuse(Canvas canvas, string name) { //IL_0034: 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) Transform val = ((Component)canvas).transform.Find(name); if ((Object)(object)val != (Object)null) { RawImage component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } Object.Destroy((Object)(object)((Component)val).gameObject); } RawImage obj = new GameObject(name) { layer = ((Component)canvas).gameObject.layer }.AddComponent(); ((Transform)((Graphic)obj).rectTransform).SetParent(((Component)canvas).transform, false); ((Graphic)obj).raycastTarget = false; return obj; } private static void Stretch(RectTransform rect, float inset = 0f) { //IL_0001: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.pivot = new Vector2(0.5f, 0.5f); rect.offsetMin = new Vector2(inset, inset); rect.offsetMax = new Vector2(0f - inset, 0f - inset); ((Transform)rect).localRotation = Quaternion.identity; ((Transform)rect).localScale = Vector3.one; Vector3 localPosition = ((Transform)rect).localPosition; ((Transform)rect).localPosition = new Vector3(localPosition.x, localPosition.y, 0f); } public void SetInset(float inset) { _inset = inset; if (IsAttached) { ApplyRect(); } } public void SetTexture(Texture texture) { if (IsAttached) { _video.texture = texture; ((Behaviour)_video).enabled = (Object)(object)texture != (Object)null; if ((Object)(object)_backdrop != (Object)null) { ((Behaviour)_backdrop).enabled = (Object)(object)texture != (Object)null; } if ((Object)(object)_effect != (Object)null) { ((Behaviour)_effect).enabled = (Object)(object)texture != (Object)null && _appliedEffect > 0.001f; } } } public void FitAspect(bool letterbox) { _letterbox = letterbox; if (IsAttached) { ApplyRect(); } } private void ApplyRect() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: 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) //IL_018b: 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) Transform parent = ((Transform)((Graphic)_video).rectTransform).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); _video.uvRect = new Rect(0f, 0f, 1f, 1f); Stretch(((Graphic)_backdrop).rectTransform, _inset); if (!_letterbox || (Object)(object)_video.texture == (Object)null || (Object)(object)val == (Object)null) { Stretch(((Graphic)_video).rectTransform, _inset); Stretch(((Graphic)_effect).rectTransform, _inset); return; } Rect rect = val.rect; Vector2 val2 = ((Rect)(ref rect)).size - new Vector2(_inset * 2f, _inset * 2f); if (val2.x <= 1f || val2.y <= 1f) { Stretch(((Graphic)_video).rectTransform, _inset); Stretch(((Graphic)_effect).rectTransform, _inset); return; } float num = (float)_video.texture.width / (float)Mathf.Max(_video.texture.height, 1); float num2 = val2.x / val2.y; Vector2 size = ((num > num2) ? new Vector2(val2.x, val2.x / num) : new Vector2(val2.y * num, val2.y)); Centre(((Graphic)_video).rectTransform, size); Centre(((Graphic)_effect).rectTransform, size); } private static void Centre(RectTransform rect, Vector2 size) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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) rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.sizeDelta = size; rect.anchoredPosition = Vector2.zero; ((Transform)rect).localRotation = Quaternion.identity; ((Transform)rect).localScale = Vector3.one; Vector3 localPosition = ((Transform)rect).localPosition; ((Transform)rect).localPosition = new Vector3(localPosition.x, localPosition.y, 0f); } public void SetLook(Color tint, float effectStrength) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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) //IL_0019: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (!IsAttached) { return; } if (tint != _appliedTint) { _appliedTint = tint; ((Graphic)_video).color = tint; } if (!Mathf.Approximately(effectStrength, _appliedEffect)) { _appliedEffect = effectStrength; if ((Object)(object)_effect != (Object)null) { ((Graphic)_effect).color = new Color(0f, 0f, 0f, Mathf.Clamp01(effectStrength)); ((Behaviour)_effect).enabled = (Object)(object)_video.texture != (Object)null && effectStrength > 0.001f; Rect rect = ((Graphic)_effect).rectTransform.rect; float num = Mathf.Max(((Rect)(ref rect)).height, 1f); _effect.uvRect = new Rect(0f, 0f, 1f, num / 4f); } } } private static Texture2D GetScanlineTexture() { //IL_0017: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_scanlines != (Object)null) { return _scanlines; } _scanlines = new Texture2D(1, 4, (TextureFormat)5, false) { name = "LethalFaceTime.Scanlines", wrapMode = (TextureWrapMode)0, filterMode = (FilterMode)1, hideFlags = (HideFlags)61 }; for (int i = 0; i < 4; i++) { bool flag = i == 0; _scanlines.SetPixel(0, i, new Color(1f, 1f, 1f, flag ? 1f : 0f)); } _scanlines.Apply(false, false); return _scanlines; } public void Detach() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) DestroyImage(ref _effect); DestroyImage(ref _video); DestroyImage(ref _backdrop); _canvas = null; _name = null; _appliedTint = Color.clear; _appliedEffect = -1f; _letterbox = false; } private static void DestroyImage(ref RawImage image) { if ((Object)(object)image != (Object)null) { try { Object.Destroy((Object)(object)((Component)image).gameObject); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error while removing a video layer: " + ex.Message)); } } image = null; } } } namespace LethalFaceTime.Patches { [HarmonyPatch(typeof(PlayerPhone))] internal static class PlayerPhonePatches { [HarmonyPostfix] [HarmonyPatch("UpdateCallingUI")] private static void UpdateCallingUIPostfix(PlayerPhone __instance) { try { FaceTimeManager.NotifyPhoneStateChanged(__instance); } catch (Exception ex) { Plugin.Log.LogError((object)("LethalFaceTime failed to handle a call state change: " + ex)); } } } } namespace LethalFaceTime.Bridges { internal static class LethalPhonesBridge { private static readonly FieldInfo ActiveCallField = AccessTools.Field(typeof(PhoneBehavior), "activeCall"); private static readonly FieldInfo ActiveCallerField = AccessTools.Field(typeof(PhoneBehavior), "activeCaller"); private static readonly HashSet WarnedKeys = new HashSet(); private static bool CanReadCallFieldsDirectly { get { if (ActiveCallField != null) { return ActiveCallerField != null; } return false; } } public static PlayerPhone GetLocalPhone() { PhoneNetworkHandler instance = PhoneNetworkHandler.Instance; if ((Object)(object)instance == (Object)null) { return null; } PlayerPhone localPhone = instance.localPhone; if (!((Object)(object)localPhone == (Object)null)) { return localPhone; } return null; } public static CallSnapshot ReadCall(PhoneBehavior phone) { if ((Object)(object)phone == (Object)null) { return CallSnapshot.Disconnected; } if (CanReadCallFieldsDirectly) { try { short value = ((NetworkVariable)ActiveCallField.GetValue(phone)).Value; if (value == -1) { return CallSnapshot.Disconnected; } ulong value2 = ((NetworkVariable)ActiveCallerField.GetValue(phone)).Value; return new CallSnapshot(value, value2, ResolvePhoneByNetworkId(value2)); } catch (Exception ex) { WarnOnce("Could not read LethalPhones call fields directly (" + ex.GetType().Name + "); falling back to the public API."); } } return ReadCallViaPublicApi(phone); } private static CallSnapshot ReadCallViaPublicApi(PhoneBehavior phone) { bool flag; try { flag = phone.IsActive(); } catch (Exception) { return CallSnapshot.Disconnected; } if (!flag) { return CallSnapshot.Disconnected; } PhoneBehavior val = null; try { val = phone.GetCallerPhone(); } catch (Exception) { } ulong remotePhoneNetworkId = (((Object)(object)val != (Object)null) ? ((NetworkBehaviour)val).NetworkObjectId : 0); return new CallSnapshot(-2, remotePhoneNetworkId, val); } private static PhoneBehavior ResolvePhoneByNetworkId(ulong networkObjectId) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.SpawnManager == null) { return null; } if (!singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var value)) { return null; } if (!((Object)(object)value == (Object)null)) { return ((Component)value).GetComponent(); } return null; } public static CallTarget ResolveTarget(PhoneBehavior remotePhone) { if ((Object)(object)remotePhone == (Object)null) { return CallTarget.None; } PlayerPhone val = (PlayerPhone)(object)((remotePhone is PlayerPhone) ? remotePhone : null); if (val != null) { PlayerControllerB player = val.player; if ((Object)(object)player == (Object)null) { return CallTarget.None; } GameObject serverPhoneModel = val.serverPhoneModel; return CallTarget.ForPlayer(player, ((Object)(object)serverPhoneModel != (Object)null) ? serverPhoneModel.transform : null); } SwitchboardPhone val2 = (SwitchboardPhone)(object)((remotePhone is SwitchboardPhone) ? remotePhone : null); if (val2 != null) { return CallTarget.ForSwitchboard(((Component)val2).transform, FindSwitchboardScreen(val2)); } EnemyPhone val3 = (EnemyPhone)(object)((remotePhone is EnemyPhone) ? remotePhone : null); if (val3 != null) { EnemyAI enemy = val3.enemy; if ((Object)(object)enemy == (Object)null) { return CallTarget.None; } return CallTarget.ForCreature(((Component)enemy).transform, enemy.eye); } return CallTarget.ForTransform(((Component)remotePhone).transform); } public static int GetLocalHandsetLayer() { PlayerPhone localPhone = GetLocalPhone(); GameObject val = (((Object)(object)localPhone != (Object)null) ? localPhone.localPhoneModel : null); if ((Object)(object)val == (Object)null) { return -1; } Renderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { return ((Component)val2).gameObject.layer; } } return -1; } public static PlayerControllerB GetSwitchboardOperator() { PhoneNetworkHandler instance = PhoneNetworkHandler.Instance; if ((Object)(object)instance == (Object)null) { return null; } SwitchboardPhone switchboard = instance.switchboard; if ((Object)(object)switchboard == (Object)null) { return null; } PlayerControllerB switchboardOperator = switchboard.switchboardOperator; if (!((Object)(object)switchboardOperator == (Object)null)) { return switchboardOperator; } return null; } public static Transform FindSwitchboardScreen(SwitchboardPhone switchboard) { if ((Object)(object)switchboard == (Object)null) { return null; } Transform val = ((Component)switchboard).transform.Find("SwitchboardScreen"); if ((Object)(object)val != (Object)null) { return val; } WarnOnce("Could not find the switchboard's SwitchboardScreen child; filming it from its root instead."); return null; } public static Canvas FindLocalScreenCanvas(PlayerPhone phone) { if ((Object)(object)phone == (Object)null) { return null; } GameObject localPhoneModel = phone.localPhoneModel; if ((Object)(object)localPhoneModel == (Object)null) { return null; } Transform val = localPhoneModel.transform.Find("LocalPhoneModel/PhoneTop/PhoneCanvas"); if (!((Object)(object)val == (Object)null)) { return ((Component)val).GetComponent(); } return null; } public static bool IsPhoneOpen(PlayerPhone phone) { if ((Object)(object)phone != (Object)null) { return phone.toggled; } return false; } public static float GetInterference(PhoneBehavior phone) { if ((Object)(object)phone == (Object)null) { return 1f; } try { return Mathf.Clamp01(phone.GetTotalInterference()); } catch (Exception) { return 0f; } } private static void WarnOnce(string message) { if (WarnedKeys.Add(message)) { Plugin.Log.LogWarning((object)message); } } } internal readonly struct CallSnapshot { public const short UnknownNumber = -2; public static readonly CallSnapshot Disconnected; public readonly short RemoteNumber; public readonly ulong RemotePhoneNetworkId; public readonly PhoneBehavior RemotePhone; public readonly bool IsConnected; public ulong Identity { get { if (!IsConnected) { return 0uL; } return RemotePhoneNetworkId; } } public CallSnapshot(short remoteNumber, ulong remotePhoneNetworkId, PhoneBehavior remotePhone) { RemoteNumber = remoteNumber; RemotePhoneNetworkId = remotePhoneNetworkId; RemotePhone = remotePhone; IsConnected = true; } } internal enum CallTargetKind { None, Player, Creature, Switchboard, Other } internal readonly struct CallTarget { public static readonly CallTarget None; public readonly CallTargetKind Kind; public readonly PlayerControllerB Player; public readonly Transform Root; public readonly Transform Focus; public readonly Transform PhoneModel; public bool IsValid { get { if (Kind != CallTargetKind.None) { return (Object)(object)Root != (Object)null; } return false; } } public bool IsPlayer => Kind == CallTargetKind.Player; private CallTarget(CallTargetKind kind, PlayerControllerB player, Transform root, Transform focus, Transform phoneModel = null) { Kind = kind; Player = player; Root = root; PhoneModel = phoneModel; Focus = focus; } public static CallTarget ForPlayer(PlayerControllerB player, Transform phoneModel = null) { if (!((Object)(object)player == (Object)null)) { return new CallTarget(CallTargetKind.Player, player, ((Component)player).transform, player.playerGlobalHead, phoneModel); } return None; } public static CallTarget ForCreature(Transform root, Transform focus) { if (!((Object)(object)root == (Object)null)) { return new CallTarget(CallTargetKind.Creature, null, root, focus); } return None; } public static CallTarget ForSwitchboard(Transform root, Transform screen) { if (!((Object)(object)root == (Object)null)) { return new CallTarget(CallTargetKind.Switchboard, null, root, screen); } return None; } public static CallTarget ForTransform(Transform transform) { if (!((Object)(object)transform == (Object)null)) { return new CallTarget(CallTargetKind.Other, null, transform, transform); } return None; } public string Describe() { switch (Kind) { case CallTargetKind.Player: if (!((Object)(object)Player != (Object)null)) { return ""; } return "player '" + Player.playerUsername + "'"; case CallTargetKind.Creature: return "creature '" + (((Object)(object)Root != (Object)null) ? ((Object)Root).name : "?") + "'"; case CallTargetKind.Switchboard: return "the switchboard"; case CallTargetKind.Other: return "transform '" + (((Object)(object)Root != (Object)null) ? ((Object)Root).name : "?") + "'"; default: return ""; } } } internal static class OpenBodyCamsBridge { private static readonly MethodInfo HasFinishedGameStartSetupMethod = AccessTools.Method(typeof(BodyCamComponent), "HasFinishedGameStartSetup", (Type[])null, (Type[])null); private static bool _reportedProbeFailure; private static bool _staticSetupConfirmed; public static bool IsReady() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.localPlayerController == (Object)null) { return false; } return HasFinishedStaticSetup(); } private static bool HasFinishedStaticSetup() { if (_staticSetupConfirmed) { return true; } if (HasFinishedGameStartSetupMethod != null) { try { if ((bool)HasFinishedGameStartSetupMethod.Invoke(null, null)) { _staticSetupConfirmed = true; } return _staticSetupConfirmed; } catch (Exception ex) { if (!_reportedProbeFailure) { _reportedProbeFailure = true; Plugin.Log.LogWarning((object)("Could not probe OpenBodyCams' readiness flag (" + ex.GetType().Name + "); falling back to public signals.")); } } } if ((Object)(object)BodyCam.MainBodyCam != (Object)null) { _staticSetupConfirmed = true; return true; } return true; } public static int ResolveOwnerOnlyLayer(int bodyCamCullingMask) { PlayerControllerB val = StartOfRound.Instance?.localPlayerController; Camera val2 = (((Object)(object)val != (Object)null) ? val.gameplayCamera : null); if ((Object)(object)val2 == (Object)null || bodyCamCullingMask == 0) { return -1; } int num = val2.cullingMask & ~bodyCamCullingMask; if (num == 0) { return -1; } for (int i = 0; i < 32; i++) { if ((num & (1 << i)) != 0) { return i; } } return -1; } public static bool BodyCamsAreUnlocked() { try { return BodyCam.BodyCamsAreAvailable; } catch (Exception) { return true; } } } }