Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CrewMonitors v1.0.0
CrewMonitors.dll
Decompiled 7 hours agousing 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<CrewSlot> _slots = new List<CrewSlot>(); 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<MeshRenderer> 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<MeshRenderer> CollectReusableRenderers() { List<MeshRenderer> result = new List<MeshRenderer>(); HashSet<int> claimed = new HashSet<int>(); if (Plugin.PreferReuseVanillaScreens.Value) { GameObject val = GameObject.Find("Environment/HangarShip/ShipModels2b/MonitorWall/SingleScreen"); TryAdd(((Object)(object)val != (Object)null) ? val.GetComponent<MeshRenderer>() : 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<MeshRenderer>(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<MeshRenderer> 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<MeshRenderer>(); if ((Object)(object)_cloneTemplate != (Object)null) { return; } } if (!((Object)(object)_monitorWall != (Object)null)) { return; } MeshRenderer[] componentsInChildren = ((Component)_monitorWall).GetComponentsInChildren<MeshRenderer>(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<MeshRenderer>(); } } 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<GameObject>(((Component)_cloneTemplate).gameObject, _monitorWall); ((Object)val).name = string.Format("{0}{1}", "CrewMonitor_Clone_", slotIndex); BodyCamComponent[] components = val.GetComponents<BodyCamComponent>(); for (int i = 0; i < components.Length; i++) { Object.Destroy((Object)(object)components[i]); } ManualCameraRenderer[] components2 = val.GetComponents<ManualCameraRenderer>(); 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<MeshRenderer>(); if ((Object)(object)renderer == (Object)null) { renderer = val.GetComponentInChildren<MeshRenderer>(); } 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<TextMeshPro>(); ((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<PlayerControllerB> 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<PlayerControllerB> GetLivingPlayers() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null) { return new List<PlayerControllerB>(); } IEnumerable<PlayerControllerB> 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<string> stack = new Stack<string>(); 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<CrewMonitorManager>() != (Object)null)) { ((Component)__instance).gameObject.AddComponent<CrewMonitorManager>(); 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<bool> Enabled { get; private set; } internal static ConfigEntry<int> MaxMonitors { get; private set; } internal static ConfigEntry<bool> IncludeLocalPlayer { get; private set; } internal static ConfigEntry<float> CloneSpacing { get; private set; } internal static ConfigEntry<float> RefreshSeconds { get; private set; } internal static ConfigEntry<bool> PreferReuseVanillaScreens { get; private set; } internal static ConfigEntry<bool> 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<bool>("General", "Enabled", true, "Master toggle for crew body-cam monitors on the ship."); MaxMonitors = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxMonitors", 8, new ConfigDescription("Maximum number of crew monitor slots (1–8).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); IncludeLocalPlayer = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeLocalPlayer", true, "Include the local player in crew monitor assignments."); CloneSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CloneSpacing", 0.42f, "Local-space spacing between cloned monitor panels along the wall."); RefreshSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RefreshSeconds", 1f, "How often (seconds) to re-assign living players to monitor slots."); PreferReuseVanillaScreens = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PreferReuseVanillaScreens", true, "Reuse unused vanilla ship screens (door SingleScreen, inside/security cam meshes) before cloning."); ShowNameplates = ((BaseUnityPlugin)this).Config.Bind<bool>("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"; } }