Decompiled source of ShipBeacon v1.0.12

ShipBeacon.dll

Decompiled 3 hours ago
using System;
using System.Diagnostics;
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 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("ShipBeacon")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6")]
[assembly: AssemblyProduct("ShipBeacon")]
[assembly: AssemblyTitle("ShipBeacon")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.6.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 ShipBeacon
{
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class StartOfRoundStartPatch
	{
		private static void Postfix()
		{
			ShipBeaconHud.EnsureExists();
			Plugin.Log.LogInfo((object)"ShipBeacon HUD ensured after StartOfRound.Start.");
		}
	}
	[HarmonyPatch(typeof(HUDManager), "Start")]
	internal static class HudManagerStartPatch
	{
		private static void Postfix()
		{
			ShipBeaconHud.EnsureExists();
		}
	}
	[BepInPlugin("com.benhough.lethal.ShipBeacon", "ShipBeacon", "1.0.12")]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "com.benhough.lethal.ShipBeacon";

		public const string ModName = "ShipBeacon";

		public const string ModVersion = "1.0.12";

		private readonly Harmony _harmony = new Harmony("com.benhough.lethal.ShipBeacon");

		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<bool> ShowDistance { get; private set; }

		internal static ConfigEntry<float> HudScale { get; private set; }

		internal static ConfigEntry<float> VerticalOffset { get; private set; }

		internal static ConfigEntry<bool> MatchClockStyle { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Show the outdoor ship beacon HUD.");
			ShowDistance = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowDistance", true, "Show distance to the ship in meters.");
			HudScale = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HudScale", 1f, "Scale of the beacon HUD text.");
			VerticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("General", "VerticalOffset", 0.1f, "Beacon text vertical position (0 = bottom, 1 = top). Default sits near the bottom of the screen.");
			MatchClockStyle = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MatchClockStyle", true, "Use the orange clock HUD color/font when available.");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			ShipBeaconHud.EnsureExists();
			Log.LogInfo((object)"ShipBeacon v1.0.12 loaded (bottom orange HUD style).");
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.benhough.lethal.ShipBeacon";

		public const string PLUGIN_NAME = "ShipBeacon";

		public const string PLUGIN_VERSION = "1.0.12";
	}
	internal sealed class ShipBeaconHud : MonoBehaviour
	{
		private static ShipBeaconHud? _instance;

		private Canvas? _canvas;

		private TextMeshProUGUI? _label;

		private RectTransform? _labelRt;

		private float _nextDebugLog;

		private string _lastHideReason = "";

		private bool _loggedVisible;

		private float _nextHeartbeat;

		private bool _fontAssigned;

		private string _lastBeaconDetail = "";

		private static readonly Color ClockOrange = new Color(0.855f, 0.4f, 0.185f, 1f);

		internal static void EnsureExists()
		{
			//IL_0013: 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_001e: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("ShipBeaconHUD");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<ShipBeaconHud>();
			}
		}

		private void Awake()
		{
			_instance = this;
			BuildUi();
		}

		private void OnDestroy()
		{
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
		}

		private void BuildUi()
		{
			//IL_0058: 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_008c: Expected O, but got Unknown
			//IL_00bf: 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_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_canvas != (Object)null))
			{
				_canvas = ((Component)this).gameObject.AddComponent<Canvas>();
				_canvas.renderMode = (RenderMode)0;
				_canvas.sortingOrder = 5000;
				CanvasScaler obj = ((Component)this).gameObject.AddComponent<CanvasScaler>();
				obj.uiScaleMode = (ScaleMode)1;
				obj.referenceResolution = new Vector2(1920f, 1080f);
				((Component)this).gameObject.AddComponent<GraphicRaycaster>();
				GameObject val = new GameObject("ShipBeaconLabel", new Type[1] { typeof(RectTransform) });
				val.transform.SetParent(((Component)this).transform, false);
				_labelRt = val.GetComponent<RectTransform>();
				_labelRt.anchorMin = new Vector2(0.5f, Plugin.VerticalOffset.Value);
				_labelRt.anchorMax = new Vector2(0.5f, Plugin.VerticalOffset.Value);
				_labelRt.pivot = new Vector2(0.5f, 0.5f);
				_labelRt.sizeDelta = new Vector2(980f, 56f);
				_labelRt.anchoredPosition = Vector2.zero;
				_label = val.AddComponent<TextMeshProUGUI>();
				((TMP_Text)_label).alignment = (TextAlignmentOptions)514;
				((TMP_Text)_label).fontStyle = (FontStyles)1;
				((Graphic)_label).color = ClockOrange;
				((TMP_Text)_label).enableWordWrapping = false;
				((Graphic)_label).raycastTarget = false;
				((TMP_Text)_label).overflowMode = (TextOverflowModes)0;
				((TMP_Text)_label).text = "";
				((TMP_Text)_label).fontSharedMaterial = null;
				TryAssignFont();
				ApplyClockStyle();
				ApplyScale();
				((Behaviour)_canvas).enabled = false;
				Plugin.Log.LogInfo((object)"ShipBeacon canvas HUD built.");
			}
		}

		private void TryAssignFont()
		{
			if ((Object)(object)_label == (Object)null || _fontAssigned)
			{
				return;
			}
			try
			{
				TMP_FontAsset val = null;
				HUDManager instance = HUDManager.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					if ((Object)(object)instance.clockNumber != (Object)null && (Object)(object)((TMP_Text)instance.clockNumber).font != (Object)null)
					{
						val = ((TMP_Text)instance.clockNumber).font;
					}
					if ((Object)(object)val == (Object)null && instance.controlTipLines != null)
					{
						TextMeshProUGUI[] controlTipLines = instance.controlTipLines;
						foreach (TextMeshProUGUI val2 in controlTipLines)
						{
							if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null)
							{
								val = ((TMP_Text)val2).font;
								break;
							}
						}
					}
					if ((Object)(object)val == (Object)null && (Object)(object)instance.weightCounter != (Object)null)
					{
						val = ((TMP_Text)instance.weightCounter).font;
					}
				}
				if ((Object)(object)val == (Object)null && (Object)(object)TMP_Settings.defaultFontAsset != (Object)null)
				{
					val = TMP_Settings.defaultFontAsset;
				}
				if ((Object)(object)val != (Object)null)
				{
					((TMP_Text)_label).font = val;
					_fontAssigned = true;
					Plugin.Log.LogInfo((object)("ShipBeacon TMP font assigned: " + ((Object)val).name));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("ShipBeacon font assign failed: " + ex.Message));
			}
		}

		private void ApplyScale()
		{
			if ((Object)(object)_label == (Object)null)
			{
				return;
			}
			float num = 26f;
			try
			{
				TextMeshProUGUI val = HUDManager.Instance?.clockNumber;
				if ((Object)(object)val != (Object)null && ((TMP_Text)val).fontSize > 1f)
				{
					num = ((TMP_Text)val).fontSize * 0.92f;
				}
			}
			catch
			{
			}
			((TMP_Text)_label).fontSize = num * Plugin.HudScale.Value;
		}

		private void ApplyClockStyle()
		{
			//IL_00ae: 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)
			if ((Object)(object)_label == (Object)null)
			{
				return;
			}
			try
			{
				((Graphic)_label).color = ClockOrange;
				((TMP_Text)_label).fontStyle = (FontStyles)1;
				if (Plugin.MatchClockStyle != null && Plugin.MatchClockStyle.Value)
				{
					HUDManager instance = HUDManager.Instance;
					TextMeshProUGUI val = (((Object)(object)instance != (Object)null) ? instance.clockNumber : null);
					if (!((Object)(object)val == (Object)null) && (Object)(object)((TMP_Text)val).font != (Object)null)
					{
						((TMP_Text)_label).font = ((TMP_Text)val).font;
						_fontAssigned = true;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("ApplyClockStyle: " + ex.Message));
				try
				{
					((Graphic)_label).color = ClockOrange;
				}
				catch
				{
				}
			}
		}

		private void LateUpdate()
		{
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_canvas == (Object)null || (Object)(object)_label == (Object)null || (Object)(object)_labelRt == (Object)null)
			{
				BuildUi();
			}
			TryAssignFont();
			ApplyClockStyle();
			ApplyScale();
			if (Time.unscaledTime >= _nextHeartbeat)
			{
				_nextHeartbeat = Time.unscaledTime + 10f;
				StartOfRound instance = StartOfRound.Instance;
				PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
				ManualLogSource log = Plugin.Log;
				object arg = Plugin.Enabled.Value;
				object arg2 = (Object)(object)_canvas != (Object)null;
				Canvas? canvas = _canvas;
				log.LogInfo((object)($"[Heartbeat] enabled={arg}, canvas={arg2}, canvasOn={((canvas != null) ? new bool?(((Behaviour)canvas).enabled) : ((bool?)null))}, " + $"font={_fontAssigned}, hide='{_lastHideReason}', detail='{_lastBeaconDetail}', " + $"inShipPhase={instance?.inShipPhase}, playerNull={(Object)(object)val == (Object)null}, dead={val?.isPlayerDead}, " + $"insideFactory={val?.isInsideFactory}, inShip={val?.isInHangarShipRoom}"));
			}
			if (Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				SetVisible(visible: false);
				_lastHideReason = "disabled";
				return;
			}
			if ((Object)(object)_labelRt != (Object)null)
			{
				float num = Mathf.Clamp01(Plugin.VerticalOffset.Value);
				_labelRt.anchorMin = new Vector2(0.5f, num);
				_labelRt.anchorMax = new Vector2(0.5f, num);
			}
			ApplyScale();
			if (!TryGetBeacon(out float signedAngleDeg, out float distance, out string hideReason))
			{
				SetVisible(visible: false);
				MaybeLogHidden(hideReason);
				_loggedVisible = false;
				return;
			}
			try
			{
				((TMP_Text)_label).text = FormatBeacon(signedAngleDeg, distance);
				SetVisible(visible: true);
				_lastHideReason = "";
				_lastBeaconDetail = $"{distance:0}m bearing={signedAngleDeg:0}";
				if (!_loggedVisible)
				{
					_loggedVisible = true;
					Plugin.Log.LogInfo((object)$"ShipBeacon visible: {distance:0}m, bearing {signedAngleDeg:0} deg.");
				}
			}
			catch (Exception ex)
			{
				SetVisible(visible: false);
				MaybeLogHidden("label update: " + ex.Message);
				_loggedVisible = false;
			}
		}

		private void SetVisible(bool visible)
		{
			if ((Object)(object)_canvas != (Object)null && ((Behaviour)_canvas).enabled != visible)
			{
				((Behaviour)_canvas).enabled = visible;
			}
			if ((Object)(object)_label != (Object)null && ((Component)_label).gameObject.activeSelf != visible)
			{
				((Component)_label).gameObject.SetActive(visible);
			}
		}

		private void MaybeLogHidden(string reason)
		{
			if (reason == null)
			{
				reason = "";
			}
			bool num = reason != _lastHideReason;
			_lastHideReason = reason;
			if (num || !(Time.unscaledTime < _nextDebugLog))
			{
				_nextDebugLog = Time.unscaledTime + 8f;
				if (!string.IsNullOrEmpty(reason))
				{
					Plugin.Log.LogInfo((object)("ShipBeacon hidden: " + reason));
				}
			}
		}

		private static bool TryGetBeacon(out float signedAngleDeg, out float distance, out string hideReason)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: 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_016c: 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)
			signedAngleDeg = 0f;
			distance = 0f;
			hideReason = "";
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					hideReason = "StartOfRound missing";
					return false;
				}
				if (instance.inShipPhase)
				{
					hideReason = "inShipPhase (orbit)";
					return false;
				}
				PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
				if ((Object)(object)val == (Object)null || val.isPlayerDead)
				{
					hideReason = "no local player / dead";
					return false;
				}
				if (val.isInsideFactory)
				{
					hideReason = "inside factory";
					return false;
				}
				if (val.isInHangarShipRoom)
				{
					hideReason = "inside ship";
					return false;
				}
				Transform val2 = ResolveShipTransform(instance);
				if ((Object)(object)val2 == (Object)null)
				{
					hideReason = "no ship transform";
					return false;
				}
				Transform val3 = (((Object)(object)val.gameplayCamera != (Object)null) ? ((Component)val.gameplayCamera).transform : ((Component)val).transform);
				Vector3 val4 = val2.position - val3.position;
				val4.y = 0f;
				distance = ((Vector3)(ref val4)).magnitude;
				if (distance < 0.5f)
				{
					hideReason = $"too close ({distance:0.0}m)";
					return false;
				}
				Vector3 forward = val3.forward;
				forward.y = 0f;
				if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
				{
					hideReason = "bad camera forward";
					return false;
				}
				signedAngleDeg = Vector3.SignedAngle(((Vector3)(ref forward)).normalized, ((Vector3)(ref val4)).normalized, Vector3.up);
				return true;
			}
			catch (Exception ex)
			{
				hideReason = ex.Message;
				return false;
			}
		}

		private static Transform? ResolveShipTransform(StartOfRound start)
		{
			try
			{
				GameObject val = GameObject.Find("/Environment/HangarShip") ?? GameObject.Find("Environment/HangarShip") ?? GameObject.Find("HangarShip");
				if ((Object)(object)val != (Object)null)
				{
					return val.transform;
				}
			}
			catch
			{
			}
			if ((Object)(object)start.shipDoorNode != (Object)null)
			{
				return start.shipDoorNode;
			}
			if ((Object)(object)start.middleOfShipNode != (Object)null)
			{
				return start.middleOfShipNode;
			}
			if ((Object)(object)start.elevatorTransform != (Object)null)
			{
				return start.elevatorTransform;
			}
			if ((Object)(object)start.shipBounds != (Object)null)
			{
				return ((Component)start.shipBounds).transform;
			}
			if ((Object)(object)start.shipLandingPosition != (Object)null)
			{
				return start.shipLandingPosition;
			}
			try
			{
				HangarShipDoor val2 = Object.FindObjectOfType<HangarShipDoor>();
				if ((Object)(object)val2 != (Object)null)
				{
					return ((Component)val2).transform;
				}
			}
			catch
			{
			}
			return null;
		}

		private static string FormatBeacon(float signedAngleDeg, float distance)
		{
			float num = Mathf.Abs(signedAngleDeg);
			string text = (Plugin.ShowDistance.Value ? $"SHIP  {distance:0}m" : "SHIP");
			if (num <= 25f)
			{
				return "^  " + text + "  ^";
			}
			if (num >= 155f)
			{
				return "v  " + text + "  v";
			}
			int count = 1;
			if (num > 45f)
			{
				count = 2;
			}
			if (num > 70f)
			{
				count = 3;
			}
			if (num > 100f)
			{
				count = 4;
			}
			if (signedAngleDeg < 0f)
			{
				return new string('<', count) + "  " + text;
			}
			return text + "  " + new string('>', count);
		}
	}
}