Decompiled source of DpsMeter v1.0.2

BepInEx/plugins/DpsMeterMod.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Pigeon.Movement;
using Sparroh.UI;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Coruscnium")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Live DPS meter HUD overlay using SparrohUILib. Shows current 1s DPS, average DPS, and peak DPS.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]
[assembly: AssemblyProduct("DPS Meter")]
[assembly: AssemblyTitle("DpsMeterMod")]
[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 DpsMeterMod
{
	[BepInPlugin("coruscnium.dpsmeter", "DpsMeter", "1.0.2.0")]
	[BepInProcess("Mycopunk.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[MycoMod(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGUID = "coruscnium.dpsmeter";

		public const string PluginName = "DpsMeter";

		public const string PluginVersion = "1.0.2.0";

		internal static ManualLogSource Logger;

		private static ConfigEntry<bool> _enabledConfig;

		private static ConfigEntry<float> _anchorXConfig;

		private static ConfigEntry<float> _anchorYConfig;

		private static ConfigColor _dpsColorConfig;

		private static ConfigColor _avgColorConfig;

		private static ConfigColor _peakColorConfig;

		private static ConfigColor _textColorConfig;

		private HudHandle _hud;

		private bool _hudBuilt;

		private bool _loggedUpdateError;

		private Harmony _harmony;

		private FileSystemWatcher _configWatcher;

		private volatile bool _reloadPending;

		private float _reloadCooldown;

		private const float ReloadDebounceSeconds = 0.25f;

		private readonly Queue<(float time, float damage)> _damageWindow = new Queue<(float, float)>();

		private float _peakDps;

		private float _lastDamageTime;

		private const float DpsWindowSeconds = 1f;

		private const float AvgWindowSeconds = 15f;

		private const float IdleResetSeconds = 3f;

		private float _hudUpdateTimer;

		private const float HudUpdateInterval = 0.25f;

		internal static Plugin Instance { get; private set; }

		private bool CanAttachHud => (Object)(object)Player.LocalPlayer != (Object)null && (Object)(object)Player.LocalPlayer.PlayerLook != (Object)null && (Object)(object)Player.LocalPlayer.PlayerLook.Reticle != (Object)null;

		private bool IsHudAlive => _hud != null && (Object)(object)_hud.GameObject != (Object)null && _hud.Lines != null && _hud.Lines.Length != 0;

		private void Awake()
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Expected O, but got Unknown
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			_enabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "If true, the DPS meter HUD is visible.");
			_anchorXConfig = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Positioning", "AnchorX", 0.03123011f, "X anchor position for DPS meter (0-1).");
			_anchorYConfig = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Positioning", "AnchorY", 0.9360327f, "Y anchor position for DPS meter (0-1).");
			_dpsColorConfig = ConfigColor.Bind(((BaseUnityPlugin)this).Config, "Colors", "DpsColor", UIColors.Rose, "Rich-text color for current DPS (hex RRGGBB or #RRGGBB).");
			_avgColorConfig = ConfigColor.Bind(((BaseUnityPlugin)this).Config, "Colors", "AvgColor", UIColors.Sky, "Rich-text color for average DPS (hex RRGGBB or #RRGGBB).");
			_peakColorConfig = ConfigColor.Bind(((BaseUnityPlugin)this).Config, "Colors", "PeakColor", UIColors.Macaroon, "Rich-text color for peak DPS (hex RRGGBB or #RRGGBB).");
			_textColorConfig = ConfigColor.Bind(((BaseUnityPlugin)this).Config, "Colors", "TextColor", UIColors.TextPrimary, "Rich-text color for stat labels (hex RRGGBB or #RRGGBB).");
			_enabledConfig.SettingChanged += delegate
			{
				UpdateHudVisibility();
			};
			_anchorXConfig.SettingChanged += delegate
			{
				UpdateAnchors();
			};
			_anchorYConfig.SettingChanged += delegate
			{
				UpdateAnchors();
			};
			((BaseUnityPlugin)this).Config.Save();
			_harmony = new Harmony("coruscnium.dpsmeter");
			ApplyPatches();
			SetupConfigHotReload();
			SceneManager.sceneLoaded += OnSceneLoaded;
			_lastDamageTime = Time.unscaledTime;
			Logger.LogInfo((object)"DpsMeter v1.0.2.0 loaded.");
		}

		private void ApplyPatches()
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(PlayerData), "OnLocalPlayerDamageTarget", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					Logger.LogError((object)"PlayerData.OnLocalPlayerDamageTarget not found. Cannot track damage without this method.");
					return;
				}
				MethodInfo method = typeof(Plugin).GetMethod("Pre_LocalPlayerDamageTarget", BindingFlags.Static | BindingFlags.NonPublic);
				if (method == null)
				{
					Logger.LogError((object)"Patch method not found.");
					return;
				}
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Logger.LogInfo((object)"Harmony patch applied: PlayerData.OnLocalPlayerDamageTarget");
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Failed to apply Harmony patch: " + ex.Message));
			}
		}

		private static void Pre_LocalPlayerDamageTarget(in DamageCallbackData data)
		{
			if (!((Object)(object)Instance == (Object)null))
			{
				float damage = data.damageData.damage;
				if (!(damage <= 0f))
				{
					Instance.RecordDamage(damage, Time.unscaledTime);
				}
			}
		}

		internal void RecordDamage(float damage, float time)
		{
			_damageWindow.Enqueue((time, damage));
			_lastDamageTime = time;
			float num = time - 15f;
			while (_damageWindow.Count > 0 && _damageWindow.Peek().time < num)
			{
				_damageWindow.Dequeue();
			}
		}

		private float GetCurrentDps(float now)
		{
			if (now - _lastDamageTime > 3f)
			{
				return 0f;
			}
			return SumWindow(now, 1f) / 1f;
		}

		private float GetAverageDps(float now)
		{
			return SumWindow(now, 15f) / 15f;
		}

		private float SumWindow(float now, float windowSec)
		{
			if (_damageWindow.Count == 0)
			{
				return 0f;
			}
			float num = now - windowSec;
			float num2 = 0f;
			foreach (var item in _damageWindow)
			{
				if (item.time >= num)
				{
					num2 += item.damage;
				}
			}
			return num2;
		}

		private void UpdatePeakDps(float currentDps)
		{
			if (currentDps > _peakDps)
			{
				_peakDps = currentDps;
			}
		}

		private void SetupConfigHotReload()
		{
			try
			{
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				string directoryName = Path.GetDirectoryName(configFilePath);
				string fileName = Path.GetFileName(configFilePath);
				if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(fileName))
				{
					Logger.LogWarning((object)"Could not set up config hot reload: invalid config path.");
					return;
				}
				_configWatcher = new FileSystemWatcher(directoryName, fileName)
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
					EnableRaisingEvents = true
				};
				_configWatcher.Changed += OnConfigFileChanged;
				_configWatcher.Created += OnConfigFileChanged;
				_configWatcher.Renamed += OnConfigFileChanged;
				Logger.LogInfo((object)("Config hot reload enabled for " + fileName));
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Failed to set up config hot reload: " + ex.Message));
			}
		}

		private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			_reloadPending = true;
		}

		private void CreateHud()
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if (IsHudAlive)
			{
				return;
			}
			ClearDestroyedHud();
			_hudBuilt = false;
			if (!CanAttachHud)
			{
				return;
			}
			_hud = HudBuilder.Create("DpsMeterHUD").ParentToReticle(true).Anchor(_anchorXConfig.Value, _anchorYConfig.Value)
				.Pivot(new Vector2(0f, 1f))
				.Size(280f, 80f, true)
				.AddLines(3, UITheme.FontHud, (TextAlignmentOptions)513)
				.Build();
			if (!IsHudAlive)
			{
				Logger.LogWarning((object)"Failed to build DPS meter HUD.");
				return;
			}
			for (int i = 1; i < _hud.Lines.Length; i++)
			{
				if (_hud.Lines[i] != null)
				{
					_hud.Lines[i].FontSize = UITheme.ScaledFontBody;
				}
			}
			_hudBuilt = true;
			try
			{
				HudRepositionAPI.Register("coruscnium.dpsmeter", "DpsMeterHUD", _hud.Rect, _anchorXConfig, _anchorYConfig);
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Failed to register HUD reposition client: " + ex.Message));
			}
			UpdateHudVisibility();
			Logger.LogInfo((object)"DPS meter HUD created.");
		}

		private void ClearDestroyedHud()
		{
			if (_hud == null)
			{
				return;
			}
			try
			{
				HudRepositionAPI.Unregister("coruscnium.dpsmeter");
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)_hud.GameObject != (Object)null)
				{
					_hud.Destroy();
				}
			}
			catch
			{
			}
			_hud = null;
			_hudBuilt = false;
		}

		private void UpdateHudVisibility()
		{
			if (!IsHudAlive)
			{
				ClearDestroyedHud();
			}
			else
			{
				_hud.SetActive(_enabledConfig.Value);
			}
		}

		private void UpdateAnchors()
		{
			if (!IsHudAlive)
			{
				ClearDestroyedHud();
			}
			else
			{
				_hud.SetAnchor(_anchorXConfig.Value, _anchorYConfig.Value);
			}
		}

		private void SetLine(int index, string text)
		{
			if (IsHudAlive && index >= 0 && index < _hud.Lines.Length)
			{
				UIText val = _hud.Lines[index];
				if (val != null && !((Object)(object)val.Tmp == (Object)null))
				{
					val.Text = text ?? string.Empty;
				}
			}
		}

		private static string FmtDps(float value)
		{
			if (value >= 1000000f)
			{
				return $"{value / 1000000f:F1}M";
			}
			if (value >= 10000f)
			{
				return $"{value / 1000f:F1}K";
			}
			if (value >= 1000f)
			{
				return $"{value:F0}";
			}
			if (value >= 100f)
			{
				return $"{value:F0}";
			}
			return $"{value:F1}";
		}

		private void Update()
		{
			if (_reloadPending)
			{
				_reloadPending = false;
				_reloadCooldown = 0.25f;
			}
			if (_reloadCooldown > 0f)
			{
				_reloadCooldown -= Time.unscaledDeltaTime;
				if (_reloadCooldown <= 0f)
				{
					TryReloadConfig();
				}
			}
			if (_enabledConfig == null || !_enabledConfig.Value)
			{
				if (IsHudAlive)
				{
					_hud.SetActive(false);
				}
				return;
			}
			if (!CanAttachHud)
			{
				if (_hudBuilt)
				{
					ClearDestroyedHud();
				}
				return;
			}
			if (!IsHudAlive)
			{
				CreateHud();
				if (!IsHudAlive)
				{
					return;
				}
			}
			if (!_hud.IsActive)
			{
				_hud.SetActive(true);
			}
			_hudUpdateTimer += Time.deltaTime;
			if (!(_hudUpdateTimer >= 0.25f))
			{
				return;
			}
			_hudUpdateTimer = 0f;
			try
			{
				RefreshHud();
			}
			catch (Exception ex)
			{
				if (!_loggedUpdateError)
				{
					_loggedUpdateError = true;
					Logger.LogError((object)("Error updating DPS HUD: " + ex.Message));
				}
			}
		}

		private void RefreshHud()
		{
			//IL_004b: 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)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_0079: 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_007e: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			float unscaledTime = Time.unscaledTime;
			float currentDps = GetCurrentDps(unscaledTime);
			float averageDps = GetAverageDps(unscaledTime);
			UpdatePeakDps(currentDps);
			string text = FmtDps(currentDps);
			string text2 = FmtDps(averageDps);
			string text3 = FmtDps(_peakDps);
			ConfigColor dpsColorConfig = _dpsColorConfig;
			Color val = ((dpsColorConfig != null) ? dpsColorConfig.Value : UIColors.Rose);
			ConfigColor avgColorConfig = _avgColorConfig;
			Color val2 = ((avgColorConfig != null) ? avgColorConfig.Value : UIColors.Sky);
			ConfigColor peakColorConfig = _peakColorConfig;
			Color val3 = ((peakColorConfig != null) ? peakColorConfig.Value : UIColors.Macaroon);
			SetLine(0, RichText.Labeled("DPS", text, val, ""));
			SetLine(1, RichText.Labeled("AVG", text2, val2, ""));
			SetLine(2, RichText.Labeled("PEAK", text3, val3, ""));
		}

		private void TryReloadConfig()
		{
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				Logger.LogInfo((object)"Config reloaded from disk.");
			}
			catch (IOException)
			{
				_reloadPending = true;
			}
			catch (Exception ex2)
			{
				Logger.LogWarning((object)("Failed to reload config: " + ex2.Message));
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			_damageWindow.Clear();
			_peakDps = 0f;
			_lastDamageTime = Time.unscaledTime;
			_loggedUpdateError = false;
			ClearDestroyedHud();
			Logger.LogInfo((object)("Tracking reset on scene: " + ((Scene)(ref scene)).name));
		}

		private void OnDestroy()
		{
			if (_configWatcher != null)
			{
				_configWatcher.EnableRaisingEvents = false;
				_configWatcher.Changed -= OnConfigFileChanged;
				_configWatcher.Created -= OnConfigFileChanged;
				_configWatcher.Renamed -= OnConfigFileChanged;
				_configWatcher.Dispose();
				_configWatcher = null;
			}
			try
			{
				if (_hud != null)
				{
					HudRepositionAPI.Unregister("coruscnium.dpsmeter");
					if ((Object)(object)_hud.GameObject != (Object)null)
					{
						_hud.Destroy();
					}
					_hud = null;
				}
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Error cleaning up HUD: " + ex.Message));
			}
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)("Error unpatching Harmony: " + ex2.Message));
			}
		}
	}
}