using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using OpenBodyCams; using OpenBodyCams.API; using TMPro; using UnityEngine; 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("CrewMonitors")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CrewMonitors")] [assembly: AssemblyTitle("CrewMonitors")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CrewMonitors { public sealed class CrewMonitorManager : MonoBehaviour { private sealed class CrewSlot { public int Index; public GameObject Host; public MeshRenderer Renderer; public BodyCamComponent BodyCam; public bool IsClone; public PlayerControllerB? AssignedPlayer; public TextMeshPro? Nameplate; } private const string MonitorWallPath = "Environment/HangarShip/ShipModels2b/MonitorWall"; private const string SingleScreenPath = "Environment/HangarShip/ShipModels2b/MonitorWall/SingleScreen"; private const string MainWallCubeName = "Cube.001"; private const string ClonePrefix = "CrewMonitor_Clone_"; private const string SlotRootName = "CrewMonitors_Root"; private readonly List _slots = new List(); private Transform? _monitorWall; private MeshRenderer? _cloneTemplate; private GameObject? _slotRoot; private bool _setupStarted; private bool _setupComplete; private bool _refreshRequested; private bool _setupSoonRequested; private float _refreshTimer; private float _setupWaitElapsed; internal static CrewMonitorManager? Instance { get; private set; } private void Awake() { Instance = this; } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } TeardownSlots(); } private void Start() { ((MonoBehaviour)this).StartCoroutine(SetupWhenReady()); } private void Update() { if (!Plugin.Enabled.Value) { if (_slots.Count > 0) { PowerDownAll(); } return; } if (_setupSoonRequested && !_setupComplete) { _setupSoonRequested = false; if (!_setupStarted) { ((MonoBehaviour)this).StartCoroutine(SetupWhenReady()); } } if (_setupComplete) { float num = Mathf.Max(0.25f, Plugin.RefreshSeconds.Value); _refreshTimer += Time.deltaTime; if (_refreshRequested || _refreshTimer >= num) { _refreshRequested = false; _refreshTimer = 0f; RefreshAssignments(); } } } internal void RequestRefresh() { _refreshRequested = true; } internal void RequestSetupSoon() { _setupSoonRequested = true; } private IEnumerator SetupWhenReady() { if (_setupStarted) { yield break; } _setupStarted = true; _setupWaitElapsed = 0f; while (_setupWaitElapsed < 12f) { GameObject val = GameObject.Find("Environment/HangarShip/ShipModels2b/MonitorWall"); bool flag = (Object)(object)BodyCam.MainBodyCam != (Object)null || BodyCamComponent.GetAllBodyCams().Length != 0; if ((Object)(object)val != (Object)null && (flag || _setupWaitElapsed > 3f)) { _monitorWall = val.transform; break; } _setupWaitElapsed += 0.25f; yield return (object)new WaitForSeconds(0.25f); } if ((Object)(object)_monitorWall == (Object)null) { Plugin.Log.LogWarning((object)"MonitorWall not found; crew monitors will not set up."); yield break; } try { BuildSlots(); _setupComplete = true; RefreshAssignments(); Plugin.Log.LogInfo((object)$"Crew monitors ready with {_slots.Count} slot(s)."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Crew monitor setup failed: {arg}"); } } private void BuildSlots() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) TeardownSlots(); int num = Mathf.Clamp(Plugin.MaxMonitors.Value, 1, 8); _slotRoot = new GameObject("CrewMonitors_Root"); _slotRoot.transform.SetParent(_monitorWall, false); _slotRoot.transform.localPosition = Vector3.zero; _slotRoot.transform.localRotation = Quaternion.identity; _slotRoot.transform.localScale = Vector3.one; List list = CollectReusableRenderers(); EnsureCloneTemplate(list); for (int i = 0; i < num; i++) { MeshRenderer renderer = null; bool flag = false; GameObject val; if (i < list.Count) { renderer = list[i]; val = ((Component)renderer).gameObject; } else { if ((Object)(object)_cloneTemplate == (Object)null) { Plugin.Log.LogWarning((object)"No template MeshRenderer available to clone; stopping slot creation."); break; } int slotIndex = i - list.Count; val = CloneMonitor(slotIndex, out renderer); flag = true; if ((Object)(object)renderer == (Object)null) { break; } } int num2 = PickMaterialIndex(renderer); BodyCamComponent val2; try { val2 = BodyCam.CreateBodyCam(val, (Renderer)(object)renderer, num2, (ManualCameraRenderer)null); } catch (Exception ex) { Plugin.Log.LogError((object)$"CreateBodyCam failed for slot {i} on {((Object)val).name}: {ex.Message}"); if (flag) { Object.Destroy((Object)(object)val); } continue; } val2.SetTargetToNone(); val2.SetScreenPowered(false); CrewSlot item = new CrewSlot { Index = i, Host = val, Renderer = renderer, BodyCam = val2, IsClone = flag, Nameplate = (Plugin.ShowNameplates.Value ? CreateNameplate(val, renderer) : null) }; _slots.Add(item); } } private List CollectReusableRenderers() { List result = new List(); HashSet claimed = new HashSet(); if (Plugin.PreferReuseVanillaScreens.Value) { GameObject val = GameObject.Find("Environment/HangarShip/ShipModels2b/MonitorWall/SingleScreen"); TryAdd(((Object)(object)val != (Object)null) ? val.GetComponent() : null, "SingleScreen"); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { TryAdd(((Object)(object)instance.insideCameraScreen != (Object)null) ? instance.insideCameraScreen.mesh : null, "insideCameraScreen.mesh"); TryAdd(((Object)(object)instance.securityCameraScreen != (Object)null) ? instance.securityCameraScreen.mesh : null, "securityCameraScreen.mesh"); } if ((Object)(object)_monitorWall != (Object)null) { MeshRenderer[] componentsInChildren = ((Component)_monitorWall).GetComponentsInChildren(true); foreach (MeshRenderer val2 in componentsInChildren) { if (!((Object)((Component)val2).gameObject).name.StartsWith("CrewMonitor_Clone_", StringComparison.Ordinal) && !(((Object)((Component)val2).gameObject).name == "Cube.001") && !((Object)((Component)val2).gameObject).name.Contains("Cube.001")) { TryAdd(val2, "MonitorWall/" + ((Object)val2).name); } } } } return result; void TryAdd(MeshRenderer? mr, string label) { if (!((Object)(object)mr == (Object)null)) { int instanceID = ((Object)mr).GetInstanceID(); if (!claimed.Contains(instanceID)) { if (IsRendererForbidden(mr)) { Plugin.Log.LogDebug((object)("Skipping " + label + " (" + ((Object)mr).name + "): claimed or reserved.")); } else { claimed.Add(instanceID); result.Add(mr); Plugin.Log.LogInfo((object)("Reusable screen: " + label + " -> " + GetPath(((Component)mr).transform))); } } } } } private static bool IsRendererForbidden(MeshRenderer mr) { if (BodyCamComponent.AnyBodyCamHasReference((Renderer)(object)mr)) { return true; } if (((Object)((Component)mr).gameObject).name == "Cube.001" || ((Object)((Component)mr).gameObject).name.Contains("Cube.001")) { return true; } ManualCameraRenderer val = StartOfRound.Instance?.mapScreen; if ((Object)(object)val != (Object)null && (Object)(object)val.mesh == (Object)(object)mr) { return true; } return false; } private void EnsureCloneTemplate(List reusable) { if (reusable.Count > 0) { _cloneTemplate = reusable[0]; return; } GameObject val = GameObject.Find("Environment/HangarShip/ShipModels2b/MonitorWall/SingleScreen"); if ((Object)(object)val != (Object)null) { _cloneTemplate = val.GetComponent(); if ((Object)(object)_cloneTemplate != (Object)null) { return; } } if (!((Object)(object)_monitorWall != (Object)null)) { return; } MeshRenderer[] componentsInChildren = ((Component)_monitorWall).GetComponentsInChildren(true); foreach (MeshRenderer val2 in componentsInChildren) { if (!(((Object)((Component)val2).gameObject).name == "Cube.001")) { _cloneTemplate = val2; return; } } Transform val3 = _monitorWall.Find("Cube.001"); if ((Object)(object)val3 != (Object)null) { _cloneTemplate = ((Component)val3).GetComponent(); } } private GameObject CloneMonitor(int slotIndex, out MeshRenderer? renderer) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) renderer = null; if ((Object)(object)_cloneTemplate == (Object)null || (Object)(object)_monitorWall == (Object)null) { return null; } GameObject val = Object.Instantiate(((Component)_cloneTemplate).gameObject, _monitorWall); ((Object)val).name = string.Format("{0}{1}", "CrewMonitor_Clone_", slotIndex); BodyCamComponent[] components = val.GetComponents(); for (int i = 0; i < components.Length; i++) { Object.Destroy((Object)(object)components[i]); } ManualCameraRenderer[] components2 = val.GetComponents(); for (int i = 0; i < components2.Length; i++) { Object.Destroy((Object)(object)components2[i]); } float value = Plugin.CloneSpacing.Value; int num = slotIndex % 4; int num2 = slotIndex / 4; Vector3 localPosition = ((Component)_cloneTemplate).transform.localPosition; val.transform.localPosition = localPosition + new Vector3((float)num * value, (float)(num2 + 1) * value * 0.85f, 0f); val.transform.localRotation = ((Component)_cloneTemplate).transform.localRotation; val.transform.localScale = ((Component)_cloneTemplate).transform.localScale; renderer = val.GetComponent(); if ((Object)(object)renderer == (Object)null) { renderer = val.GetComponentInChildren(); } val.SetActive(true); Plugin.Log.LogInfo((object)$"Cloned monitor slot {slotIndex} at local {val.transform.localPosition}"); return val; } private static int PickMaterialIndex(MeshRenderer renderer) { Material[] sharedMaterials = ((Renderer)renderer).sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { return 0; } if (sharedMaterials.Length > 1) { return 1; } return 0; } private TextMeshPro? CreateNameplate(GameObject host, MeshRenderer renderer) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_00dd: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = new GameObject("CrewMonitor_Nameplate"); val.transform.SetParent(host.transform, false); Bounds localBounds = ((Renderer)renderer).localBounds; val.transform.localPosition = new Vector3(0f, ((Bounds)(ref localBounds)).min.y - 0.05f, ((Bounds)(ref localBounds)).center.z); val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one * 0.02f; TextMeshPro val2 = val.AddComponent(); ((TMP_Text)val2).text = ""; ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; ((TMP_Text)val2).fontSize = 36f; ((Graphic)val2).color = new Color(1f, 0.55f, 0.15f, 1f); ((TMP_Text)val2).enableAutoSizing = false; ((TMP_Text)val2).rectTransform.sizeDelta = new Vector2(20f, 4f); StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { TextMeshProUGUI mapScreenPlayerName = instance.mapScreenPlayerName; obj = ((mapScreenPlayerName != null) ? ((TMP_Text)mapScreenPlayerName).font : null); } TMP_FontAsset val3 = (TMP_FontAsset)obj; if ((Object)(object)val3 != (Object)null) { ((TMP_Text)val2).font = val3; } return val2; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Nameplate create failed: " + ex.Message)); return null; } } private void RefreshAssignments() { if (!_setupComplete || _slots.Count == 0) { return; } List livingPlayers = GetLivingPlayers(); for (int i = 0; i < _slots.Count; i++) { CrewSlot crewSlot = _slots[i]; if ((Object)(object)crewSlot.BodyCam == (Object)null) { continue; } if (i < livingPlayers.Count) { PlayerControllerB val = livingPlayers[i]; if ((Object)(object)crewSlot.AssignedPlayer != (Object)(object)val) { crewSlot.BodyCam.SetTargetToPlayer(val); crewSlot.AssignedPlayer = val; } crewSlot.BodyCam.SetScreenPowered(true); if (crewSlot.IsClone && (Object)(object)crewSlot.Host != (Object)null && !crewSlot.Host.activeSelf) { crewSlot.Host.SetActive(true); } if ((Object)(object)crewSlot.Nameplate != (Object)null) { ((Component)crewSlot.Nameplate).gameObject.SetActive(true); ((TMP_Text)crewSlot.Nameplate).text = (string.IsNullOrEmpty(val.playerUsername) ? $"Player {val.playerClientId}" : val.playerUsername); } } else { crewSlot.AssignedPlayer = null; crewSlot.BodyCam.SetTargetToNone(); crewSlot.BodyCam.SetScreenPowered(false); if (crewSlot.IsClone && (Object)(object)crewSlot.Host != (Object)null && crewSlot.Host.activeSelf) { crewSlot.Host.SetActive(false); } if ((Object)(object)crewSlot.Nameplate != (Object)null) { ((TMP_Text)crewSlot.Nameplate).text = ""; ((Component)crewSlot.Nameplate).gameObject.SetActive(false); } } } } private static List GetLivingPlayers() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null) { return new List(); } IEnumerable source = instance.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerControlled && !p.isPlayerDead); if (!Plugin.IncludeLocalPlayer.Value) { PlayerControllerB local = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)local != (Object)null) { source = source.Where((PlayerControllerB p) => (Object)(object)p != (Object)(object)local); } } return source.OrderBy((PlayerControllerB p) => p.playerClientId).ToList(); } private void PowerDownAll() { foreach (CrewSlot slot in _slots) { if (!((Object)(object)slot.BodyCam == (Object)null)) { slot.BodyCam.SetTargetToNone(); slot.BodyCam.SetScreenPowered(false); slot.AssignedPlayer = null; if (slot.IsClone && (Object)(object)slot.Host != (Object)null) { slot.Host.SetActive(false); } if ((Object)(object)slot.Nameplate != (Object)null) { ((Component)slot.Nameplate).gameObject.SetActive(false); } } } } private void TeardownSlots() { foreach (CrewSlot slot in _slots) { if ((Object)(object)slot.BodyCam != (Object)null) { try { slot.BodyCam.SetTargetToNone(); slot.BodyCam.SetScreenPowered(false); } catch { } Object.Destroy((Object)(object)slot.BodyCam); } if ((Object)(object)slot.Nameplate != (Object)null) { Object.Destroy((Object)(object)((Component)slot.Nameplate).gameObject); } if (slot.IsClone && (Object)(object)slot.Host != (Object)null) { Object.Destroy((Object)(object)slot.Host); } } _slots.Clear(); if ((Object)(object)_slotRoot != (Object)null) { Object.Destroy((Object)(object)_slotRoot); _slotRoot = null; } } private static string GetPath(Transform t) { Stack stack = new Stack(); while ((Object)(object)t != (Object)null) { stack.Push(((Object)t).name); t = t.parent; } return string.Join("/", stack); } } [HarmonyPatch(typeof(StartOfRound))] internal static class StartOfRoundPatches { [HarmonyPostfix] [HarmonyPatch("Start")] private static void StartPostfix(StartOfRound __instance) { if (Plugin.Enabled.Value && !((Object)(object)((Component)__instance).GetComponent() != (Object)null)) { ((Component)__instance).gameObject.AddComponent(); Plugin.Log.LogInfo((object)"CrewMonitorManager attached after StartOfRound.Start."); } } [HarmonyPostfix] [HarmonyPatch("OnDestroy")] private static void OnDestroyPostfix() { } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class PlayerPatches { [HarmonyPostfix] [HarmonyPatch("KillPlayerClientRpc")] private static void KillPlayerClientRpcPostfix() { CrewMonitorManager.Instance?.RequestRefresh(); } [HarmonyPostfix] [HarmonyPatch("ConnectClientToPlayerObject")] private static void ConnectClientToPlayerObjectPostfix() { CrewMonitorManager.Instance?.RequestSetupSoon(); } } [BepInPlugin("com.benhough.lethal.CrewMonitors", "CrewMonitors", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.benhough.lethal.CrewMonitors"; public const string ModName = "CrewMonitors"; public const string ModVersion = "1.0.0"; public const string OpenBodyCamsGuid = "Zaggy1024.OpenBodyCams"; private readonly Harmony _harmony = new Harmony("com.benhough.lethal.CrewMonitors"); internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry MaxMonitors { get; private set; } internal static ConfigEntry IncludeLocalPlayer { get; private set; } internal static ConfigEntry CloneSpacing { get; private set; } internal static ConfigEntry RefreshSeconds { get; private set; } internal static ConfigEntry PreferReuseVanillaScreens { get; private set; } internal static ConfigEntry ShowNameplates { get; private set; } private void Awake() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master toggle for crew body-cam monitors on the ship."); MaxMonitors = ((BaseUnityPlugin)this).Config.Bind("General", "MaxMonitors", 8, new ConfigDescription("Maximum number of crew monitor slots (1–8).", (AcceptableValueBase)(object)new AcceptableValueRange(1, 8), Array.Empty())); IncludeLocalPlayer = ((BaseUnityPlugin)this).Config.Bind("General", "IncludeLocalPlayer", true, "Include the local player in crew monitor assignments."); CloneSpacing = ((BaseUnityPlugin)this).Config.Bind("General", "CloneSpacing", 0.42f, "Local-space spacing between cloned monitor panels along the wall."); RefreshSeconds = ((BaseUnityPlugin)this).Config.Bind("General", "RefreshSeconds", 1f, "How often (seconds) to re-assign living players to monitor slots."); PreferReuseVanillaScreens = ((BaseUnityPlugin)this).Config.Bind("General", "PreferReuseVanillaScreens", true, "Reuse unused vanilla ship screens (door SingleScreen, inside/security cam meshes) before cloning."); ShowNameplates = ((BaseUnityPlugin)this).Config.Bind("General", "ShowNameplates", true, "Show a simple world-space nameplate under each crew monitor."); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"CrewMonitors v1.0.0 loaded (depends on Zaggy1024.OpenBodyCams)."); } } internal static class PluginInfo { public const string PLUGIN_GUID = "com.benhough.lethal.CrewMonitors"; public const string PLUGIN_NAME = "CrewMonitors"; public const string PLUGIN_VERSION = "1.0.0"; } }