Decompiled source of Speedometer v0.1.2

BepInEx/plugins/HowToFish.Speedometer/HowToFish.Speedometer.dll

Decompiled 29 minutes ago
using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: AssemblyDescription("Shows separate player-relative and world-space speeds in Unity units per second.")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyCompany("evansvl")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace HowToFish.Speedometer;

[BepInPlugin("community.howtofish.speedometer", "Speedometer", "0.1.2")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "community.howtofish.speedometer";

	public const string PluginName = "Speedometer";

	public const string PluginVersion = "0.1.2";

	private static readonly FieldInfo MainCanvasField = typeof(PlayerUI).GetField("_mainCanvas", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo BossNameField = typeof(BossUI).GetField("_bossNameText", BindingFlags.Instance | BindingFlags.NonPublic);

	private ConfigEntry<bool> _enabled;

	private ConfigEntry<bool> _showMovementSpeed;

	private ConfigEntry<bool> _showWorldSpeed;

	private ConfigEntry<bool> _includeVertical;

	private ConfigEntry<bool> _showLabel;

	private ConfigEntry<int> _decimalPlaces;

	private ConfigEntry<float> _smoothingSeconds;

	private ConfigEntry<float> _scale;

	private ConfigEntry<float> _opacity;

	private ConfigEntry<float> _rightOffset;

	private ConfigEntry<float> _bottomOffset;

	private RectTransform _root;

	private CanvasGroup _group;

	private TextMeshProUGUI _movementText;

	private TextMeshProUGUI _worldText;

	private Player _trackedPlayer;

	private Vector3 _lastWorldPosition;

	private bool _hasWorldPosition;

	private float _smoothedMovementSpeed;

	private float _smoothedWorldSpeed;

	private bool _hasMovementSample;

	private bool _hasWorldSpeedSample;

	private void Awake()
	{
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Expected O, but got Unknown
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0112: Expected O, but got Unknown
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Expected O, but got Unknown
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_0190: Expected O, but got Unknown
		//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cf: Expected O, but got Unknown
		//IL_0204: Unknown result type (might be due to invalid IL or missing references)
		//IL_020e: Expected O, but got Unknown
		_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "Enabled", true, "Show the local-player speedometer.");
		_showMovementSpeed = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowMovementSpeed", true, "Show MOVE speed relative to the ground or boat.");
		_showWorldSpeed = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowWorldSpeed", true, "Show WORLD speed measured from the player's physical world position.");
		_includeVertical = ((BaseUnityPlugin)this).Config.Bind<bool>("Speed", "IncludeVertical", false, "Include upward and downward movement in WORLD speed. MOVE speed remains horizontal.");
		_showLabel = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowLabel", true, "Show the MOVE and WORLD labels.");
		_decimalPlaces = ((BaseUnityPlugin)this).Config.Bind<int>("HUD", "DecimalPlaces", 2, new ConfigDescription("Number of decimal places.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), new object[0]));
		_smoothingSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Speed", "SmoothingSeconds", 0.08f, new ConfigDescription("Display smoothing time. Set to zero for the raw value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), new object[0]));
		_scale = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Scale", 1f, new ConfigDescription("Speedometer text scale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.6f, 1.8f), new object[0]));
		_opacity = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Opacity", 1f, new ConfigDescription("Speedometer opacity.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), new object[0]));
		_rightOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "RightOffset", 36f, new ConfigDescription("Distance from the right edge of the screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), new object[0]));
		_bottomOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "BottomOffset", 70f, new ConfigDescription("Distance from the bottom edge of the screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 500f), new object[0]));
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded. Speedometer shows movement-relative and physical world-space speed.");
	}

	private void Update()
	{
		//IL_0083: 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_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: 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_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: 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_00f0: 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_00fc: 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_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.LocalPlayer;
		if (!_enabled.Value || !IsGameplayHudVisible(localPlayer))
		{
			ResetSample();
			SetVisible(visible: false);
			return;
		}
		if (!EnsureHud())
		{
			SetVisible(visible: false);
			return;
		}
		if ((Object)(object)_trackedPlayer != (Object)(object)localPlayer)
		{
			ResetSample();
			_trackedPlayer = localPlayer;
		}
		PlayerMovement movement = localPlayer.Movement;
		Rigidbody rigidbody = localPlayer.Rigidbody;
		if ((Object)(object)movement == (Object)null || (Object)(object)rigidbody == (Object)null)
		{
			ResetSample();
			SetVisible(visible: false);
			return;
		}
		Vector3 velocity = movement.Velocity;
		Vector2 val = new Vector2(velocity.x, velocity.z);
		float num = ((Vector2)(ref val)).magnitude;
		if (float.IsNaN(num) || float.IsInfinity(num))
		{
			num = 0f;
		}
		Vector3 position = rigidbody.position;
		float unscaledDeltaTime = Time.unscaledDeltaTime;
		Vector3 val2 = Vector3.zero;
		if (_hasWorldPosition && unscaledDeltaTime > 0f && unscaledDeltaTime <= 0.25f && IsFinite(position))
		{
			val2 = (position - _lastWorldPosition) / unscaledDeltaTime;
		}
		_lastWorldPosition = position;
		_hasWorldPosition = IsFinite(position);
		float magnitude;
		if (!_includeVertical.Value)
		{
			Vector2 val3 = new Vector2(val2.x, val2.z);
			magnitude = ((Vector2)(ref val3)).magnitude;
		}
		else
		{
			magnitude = ((Vector3)(ref val2)).magnitude;
		}
		float num2 = magnitude;
		if (float.IsNaN(num2) || float.IsInfinity(num2))
		{
			num2 = 0f;
		}
		float value = _smoothingSeconds.Value;
		_smoothedMovementSpeed = Smooth(_smoothedMovementSpeed, num, value, ref _hasMovementSample);
		_smoothedWorldSpeed = Smooth(_smoothedWorldSpeed, num2, value, ref _hasWorldSpeedSample);
		int num3 = Mathf.Clamp(_decimalPlaces.Value, 0, 3);
		string text = "F" + num3;
		string text2 = _smoothedMovementSpeed.ToString(text, CultureInfo.InvariantCulture) + " u/s";
		string text3 = _smoothedWorldSpeed.ToString(text, CultureInfo.InvariantCulture) + " u/s";
		((TMP_Text)_movementText).text = (_showLabel.Value ? ("MOVE   " + text2) : text2);
		((TMP_Text)_worldText).text = (_showLabel.Value ? ("WORLD  " + text3) : text3);
		ApplyLiveSettings();
		SetVisible(_showMovementSpeed.Value || _showWorldSpeed.Value);
	}

	private static float Smooth(float displayed, float raw, float smoothing, ref bool hasSample)
	{
		if (!hasSample || smoothing <= 0f)
		{
			hasSample = true;
			return raw;
		}
		float num = Mathf.Max(0f, Time.unscaledDeltaTime);
		float num2 = ((num <= 0f) ? 0f : (1f - Mathf.Exp((0f - num) / smoothing)));
		return Mathf.Lerp(displayed, raw, num2);
	}

	private static bool IsGameplayHudVisible(Player player)
	{
		if ((Object)(object)player == (Object)null || !Player.LocalPlayerEnabled || MainMenuManager.IsInMenu || PauseManager.IsPaused)
		{
			return false;
		}
		try
		{
			return !PlayerUI.UIDisabled;
		}
		catch
		{
			return true;
		}
	}

	private static bool IsFinite(Vector3 value)
	{
		if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z))
		{
			return !float.IsInfinity(value.z);
		}
		return false;
	}

	private bool EnsureHud()
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Expected O, but got Unknown
		//IL_00c9: 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_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: 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_0107: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_root != (Object)null && (Object)(object)_group != (Object)null && (Object)(object)_movementText != (Object)null && (Object)(object)_worldText != (Object)null)
		{
			return true;
		}
		DestroyHud();
		CanvasGroup val = FindMainCanvas();
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		GameObject val2 = new GameObject("SpeedometerHUD", new Type[2]
		{
			typeof(RectTransform),
			typeof(CanvasGroup)
		});
		val2.transform.SetParent(((Component)val).transform, false);
		val2.transform.SetAsLastSibling();
		_root = val2.GetComponent<RectTransform>();
		_group = val2.GetComponent<CanvasGroup>();
		RectTransform root = _root;
		Vector2 anchorMin = (_root.anchorMax = new Vector2(1f, 0f));
		root.anchorMin = anchorMin;
		_root.pivot = new Vector2(1f, 0f);
		_root.sizeDelta = new Vector2(620f, 112f);
		TextMeshProUGUI template = FindTextTemplate();
		_movementText = CreateSpeedText("MovementSpeedText", 56f, template);
		_worldText = CreateSpeedText("WorldSpeedText", 4f, template);
		ApplyLiveSettings();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Created native-style MOVE and WORLD speed HUD.");
		return true;
	}

	private TextMeshProUGUI CreateSpeedText(string name, float bottom, TextMeshProUGUI template)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Expected O, but got Unknown
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: 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_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: 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_012a: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[3]
		{
			typeof(RectTransform),
			typeof(CanvasRenderer),
			typeof(TextMeshProUGUI)
		});
		val.transform.SetParent((Transform)(object)_root, false);
		TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
		RectTransform rectTransform = ((TMP_Text)component).rectTransform;
		Vector2 anchorMin = (rectTransform.anchorMax = new Vector2(1f, 0f));
		rectTransform.anchorMin = anchorMin;
		rectTransform.pivot = new Vector2(1f, 0f);
		rectTransform.anchoredPosition = new Vector2(0f, bottom);
		rectTransform.sizeDelta = new Vector2(620f, 52f);
		if ((Object)(object)template != (Object)null && (Object)(object)((TMP_Text)template).font != (Object)null)
		{
			((TMP_Text)component).font = ((TMP_Text)template).font;
			if ((Object)(object)((TMP_Text)template).fontSharedMaterial != (Object)null)
			{
				((TMP_Text)component).fontSharedMaterial = ((TMP_Text)template).fontSharedMaterial;
			}
			((TMP_Text)component).characterSpacing = ((TMP_Text)template).characterSpacing;
		}
		((TMP_Text)component).fontSize = 34f;
		((TMP_Text)component).alignment = (TextAlignmentOptions)4100;
		((TMP_Text)component).overflowMode = (TextOverflowModes)0;
		((Graphic)component).raycastTarget = false;
		((TMP_Text)component).outlineColor = new Color32((byte)20, (byte)18, (byte)18, byte.MaxValue);
		((TMP_Text)component).outlineWidth = 0.18f;
		((TMP_Text)component).text = string.Empty;
		return component;
	}

	private static CanvasGroup FindMainCanvas()
	{
		if (MainCanvasField == null)
		{
			return null;
		}
		PlayerUI[] array = Resources.FindObjectsOfTypeAll<PlayerUI>();
		PlayerUI[] array2 = array;
		foreach (PlayerUI val in array2)
		{
			if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
			{
				object? value = MainCanvasField.GetValue(val);
				CanvasGroup val2 = (CanvasGroup)((value is CanvasGroup) ? value : null);
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
		}
		return null;
	}

	private static TextMeshProUGUI FindTextTemplate()
	{
		if (BossNameField != null)
		{
			BossUI[] array = Resources.FindObjectsOfTypeAll<BossUI>();
			BossUI[] array2 = array;
			foreach (BossUI val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					object? value = BossNameField.GetValue(val);
					TextMeshProUGUI val2 = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
					if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null)
					{
						return val2;
					}
				}
			}
		}
		TextMeshProUGUI[] array3 = Resources.FindObjectsOfTypeAll<TextMeshProUGUI>();
		TextMeshProUGUI[] array4 = array3;
		foreach (TextMeshProUGUI val3 in array4)
		{
			if ((Object)(object)val3 != (Object)null && (Object)(object)((TMP_Text)val3).font != (Object)null && ((TMP_Text)val3).text != null && ((TMP_Text)val3).text.IndexOf("Version", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return val3;
			}
		}
		return null;
	}

	private void ApplyLiveSettings()
	{
		//IL_0056: 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_0076: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_root == (Object)null) && !((Object)(object)_group == (Object)null) && !((Object)(object)_movementText == (Object)null) && !((Object)(object)_worldText == (Object)null))
		{
			_root.anchoredPosition = new Vector2(0f - _rightOffset.Value, _bottomOffset.Value);
			((Transform)_root).localScale = Vector3.one * _scale.Value;
			_group.alpha = _opacity.Value;
			((Graphic)_movementText).color = new Color(1f, 1f, 1f, 1f);
			((Graphic)_worldText).color = new Color(1f, 1f, 1f, 1f);
			((Behaviour)_movementText).enabled = _showMovementSpeed.Value;
			((Behaviour)_worldText).enabled = _showWorldSpeed.Value;
		}
	}

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

	private void ResetSample()
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		_trackedPlayer = null;
		_hasWorldPosition = false;
		_lastWorldPosition = Vector3.zero;
		_hasMovementSample = false;
		_hasWorldSpeedSample = false;
		_smoothedMovementSpeed = 0f;
		_smoothedWorldSpeed = 0f;
	}

	private void DestroyHud()
	{
		if ((Object)(object)_root != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_root).gameObject);
		}
		_root = null;
		_group = null;
		_movementText = null;
		_worldText = null;
	}

	private void OnDestroy()
	{
		DestroyHud();
	}
}