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 _enabledConfig; private static ConfigEntry _anchorXConfig; private static ConfigEntry _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("General", "Enabled", true, "If true, the DPS meter HUD is visible."); _anchorXConfig = ((BaseUnityPlugin)this).Config.Bind("HUD Positioning", "AnchorX", 0.03123011f, "X anchor position for DPS meter (0-1)."); _anchorYConfig = ((BaseUnityPlugin)this).Config.Bind("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)); } } } }