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));
}
}
}
}