using System; using System.Diagnostics; using System.IO; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon.Movement; using Sparroh.UI; using TMPro; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("IncursionTracker")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("IncursionTracker")] [assembly: AssemblyTitle("IncursionTracker")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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; } } } public static class HudRepositionClient { private const string ApiTypeName = "HudRepositionAPI"; private static Type _apiType; private static MethodInfo _register; private static MethodInfo _unregister; private static bool _available; public static bool IsAvailable { get { EnsureResolved(); return _available; } } public static void Register(string id, string displayName, RectTransform rect, ConfigEntry anchorX, ConfigEntry anchorY) { if (!_available) { EnsureResolved(force: true); } if (!_available || (Object)(object)rect == (Object)null || anchorX == null || anchorY == null) { return; } try { _register.Invoke(null, new object[5] { id, displayName, rect, anchorX, anchorY }); } catch (Exception ex) { Debug.LogWarning((object)("[HudRepositionClient] Register failed: " + (ex.InnerException?.Message ?? ex.Message))); } } public static void Unregister(string id) { if (!_available) { EnsureResolved(force: true); } if (!_available || string.IsNullOrEmpty(id)) { return; } try { _unregister.Invoke(null, new object[1] { id }); } catch (Exception) { } } private static void EnsureResolved(bool force = false) { if (_available && !force) { return; } _apiType = null; _register = null; _unregister = null; _available = false; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType("HudRepositionAPI", throwOnError: false); if (type != null) { _apiType = type; break; } } catch { } } if (_apiType == null) { return; } MethodInfo[] methods = _apiType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "Register" && parameters.Length == 5 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(string) && typeof(RectTransform).IsAssignableFrom(parameters[2].ParameterType)) { _register = methodInfo; } else if (methodInfo.Name == "Unregister" && parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { _unregister = methodInfo; } } _available = _register != null && _unregister != null; } } [BepInPlugin("sparroh.incursiontracker", "IncursionTracker", "1.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [MycoMod(/*Could not decode attribute arguments.*/)] public class SparrohPlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.incursiontracker"; public const string PluginName = "IncursionTracker"; public const string PluginVersion = "1.0.1"; internal static ManualLogSource Logger; private Harmony harmony; private IncursionTrackerLoader incursionTracker; private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; try { harmony = new Harmony("sparroh.incursiontracker"); } catch (Exception ex) { Logger.LogError((object)("Failed to create Harmony instance: " + ex.Message)); return; } ConfigFile configFile = ((BaseUnityPlugin)this).Config; try { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.incursiontracker.cfg"); fileSystemWatcher.Changed += delegate { configFile.Reload(); }; fileSystemWatcher.EnableRaisingEvents = true; } catch (Exception ex2) { Logger.LogWarning((object)("Failed to set up config watcher: " + ex2.Message)); } try { incursionTracker = new IncursionTrackerLoader(configFile, harmony); } catch (Exception ex3) { Logger.LogError((object)("Failed to initialize IncursionTracker: " + ex3.Message)); } try { harmony.PatchAll(); } catch (Exception ex4) { Logger.LogError((object)("Failed to apply Harmony patches: " + ex4.Message)); } Logger.LogInfo((object)"IncursionTracker loaded successfully."); } private void Update() { try { if (incursionTracker != null) { incursionTracker.UpdateHudVisibility(); } } catch (Exception ex) { Logger.LogError((object)("Error in IncursionTracker.UpdateHudVisibility(): " + ex.Message)); } try { if (incursionTracker != null) { incursionTracker.Update(); } } catch (Exception ex2) { Logger.LogError((object)("Error in IncursionTracker.Update(): " + ex2.Message)); } } private void OnDestroy() { try { if (incursionTracker != null) { incursionTracker.OnDestroy(); } } catch (Exception ex) { Logger.LogError((object)("Error in IncursionTracker.OnDestroy(): " + ex.Message)); } try { if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex2) { Logger.LogError((object)("Error unpatching Harmony: " + ex2.Message)); } } } public class IncursionTrackerHUD { private ConfigEntry enableHud; private ConfigEntry stopAtFloor30; private ConfigEntry trackerAnchorX; private ConfigEntry trackerAnchorY; private ConfigColor valueColor; private ConfigColor frozenColor; private HudHandle hud; private readonly ConfigFile configFile; private bool isTracking; private bool isFrozen; private bool isMissionTimerPaused; private float accumulatedMissionTime; private float missionSegmentStartTime; private float frozenMissionTime; private float remainingTime; private float frozenRemainingTime; private float lastRemainingTimerTickTime; private int floor; private int abominationsKilled; private int leversPulled; private int codesInput; private const float RemainingTimerPauseGraceSeconds = 1.5f; public static IncursionTrackerHUD Instance { get; private set; } private bool IsHudAlive { get { if (hud != null && (Object)(object)hud.GameObject != (Object)null && hud.Lines != null) { return hud.Lines.Length >= 6; } return false; } } public bool IsTracking => isTracking; public bool IsFrozen => isFrozen; public bool IsActive { get { if (IsHudAlive) { return hud.IsActive; } return false; } } public IncursionTrackerHUD(ConfigFile configFile) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) this.configFile = configFile; Instance = this; try { enableHud = configFile.Bind("General", "EnableIncursionTrackerHUD", true, "Enables the Incursion tracker HUD display."); stopAtFloor30 = configFile.Bind("General", "StopTrackingAtFloor30", false, "When enabled, freezes all trackers once the player reaches floor 30."); trackerAnchorX = configFile.Bind("HUD Positioning", "IncursionTrackerAnchorX", 0.6512445f, "X anchor position for Incursion Tracker (0-1)."); trackerAnchorY = configFile.Bind("HUD Positioning", "IncursionTrackerAnchorY", 0.93617f, "Y anchor position for Incursion Tracker (0-1)."); trackerAnchorX.SettingChanged += OnAnchorChanged; trackerAnchorY.SettingChanged += OnAnchorChanged; valueColor = ConfigColor.Bind(configFile, "Colors", "ValueColor", UIColors.Amber, "Rich-text value color while tracking (hex RRGGBB or #RRGGBB)."); frozenColor = ConfigColor.Bind(configFile, "Colors", "FrozenColor", UIColors.TextMuted, "Rich-text value color when trackers are frozen (hex RRGGBB or #RRGGBB)."); enableHud.SettingChanged += OnEnableHudChanged; } catch (Exception ex) { SparrohPlugin.Logger.LogError((object)("Failed to initialize IncursionTrackerHUD: " + ex.Message)); } } public void UpdateHudVisibility() { if (!IsHudAlive) { ClearDestroyedHud(); return; } bool active = enableHud.Value && isTracking; hud.SetActive(active); } private void OnEnableHudChanged(object sender, EventArgs e) { if (!enableHud.Value && hud != null) { DestroyHud(); } UpdateHudVisibility(); } private void OnAnchorChanged(object sender, EventArgs e) { if (IsHudAlive) { hud.SetAnchor(trackerAnchorX.Value, trackerAnchorY.Value); } } private void ClearDestroyedHud() { if (hud == null) { return; } try { if ((Object)(object)hud.Rect != (Object)null) { HudRepositionClient.Unregister("sparroh.incursiontracker"); } } catch { } hud = null; } public void StartTracking() { isTracking = true; isFrozen = false; isMissionTimerPaused = false; accumulatedMissionTime = 0f; missionSegmentStartTime = Time.realtimeSinceStartup; frozenMissionTime = 0f; remainingTime = 0f; frozenRemainingTime = 0f; lastRemainingTimerTickTime = Time.realtimeSinceStartup; floor = 0; abominationsKilled = 0; leversPulled = 0; codesInput = 0; if ((Object)(object)IncursionObjective.Instance != (Object)null) { floor = IncursionObjective.Instance.CurrentFloor; } SparrohPlugin.Logger.LogInfo((object)"Incursion tracker started"); UpdateHudVisibility(); } public void StopTracking() { isTracking = false; isFrozen = false; isMissionTimerPaused = false; SparrohPlugin.Logger.LogInfo((object)"Incursion tracker stopped"); UpdateHudVisibility(); } public void SetFloor(int newFloor) { if (isTracking && !isFrozen) { floor = newFloor; TryFreezeAtFloor30(); } } public void SetRemainingTime(float time) { if (isTracking && !isFrozen) { remainingTime = Mathf.Max(0f, time); lastRemainingTimerTickTime = Time.realtimeSinceStartup; ResumeMissionTimer(); } } public void OnAbominationKilled() { if (isTracking && !isFrozen) { abominationsKilled++; SparrohPlugin.Logger.LogInfo((object)$"Abomination killed. Total: {abominationsKilled}"); } } public void OnLeverPulled() { if (isTracking && !isFrozen) { leversPulled++; SparrohPlugin.Logger.LogInfo((object)$"Lever pulled. Total: {leversPulled}"); } } public void OnCodeInput() { if (isTracking && !isFrozen) { codesInput++; SparrohPlugin.Logger.LogInfo((object)$"Door code input. Total: {codesInput}"); } } private void TryFreezeAtFloor30() { if (stopAtFloor30.Value && !isFrozen && floor >= 30) { isFrozen = true; PauseMissionTimer(); frozenMissionTime = GetCurrentMissionTime(); frozenRemainingTime = remainingTime; SparrohPlugin.Logger.LogInfo((object)$"Incursion trackers frozen at floor {floor}"); } } private float GetCurrentMissionTime() { if (isMissionTimerPaused) { return accumulatedMissionTime; } return accumulatedMissionTime + (Time.realtimeSinceStartup - missionSegmentStartTime); } private void PauseMissionTimer() { if (!isMissionTimerPaused) { accumulatedMissionTime += Time.realtimeSinceStartup - missionSegmentStartTime; isMissionTimerPaused = true; } } private void ResumeMissionTimer() { if (isMissionTimerPaused) { missionSegmentStartTime = Time.realtimeSinceStartup; isMissionTimerPaused = false; } } private void UpdateMissionTimerPauseState() { if (!isFrozen) { if (Time.realtimeSinceStartup - lastRemainingTimerTickTime > 1.5f) { PauseMissionTimer(); } else { ResumeMissionTimer(); } } } private void CreateTrackerHUD() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (!IsHudAlive) { ClearDestroyedHud(); hud = HudBuilder.Create("IncursionTrackerHUD").ParentToReticle(true).Anchor(trackerAnchorX.Value, trackerAnchorY.Value) .Pivot(new Vector2(0f, 1f)) .Size(280f, 140f, true) .AddLines(6, 16f, (TextAlignmentOptions)257) .Build(); if (IsHudAlive) { HudRepositionClient.Register("sparroh.incursiontracker", "Incursion Tracker", hud.Rect, trackerAnchorX, trackerAnchorY); UpdateHudVisibility(); } } } private void DestroyHud() { HudRepositionClient.Unregister("sparroh.incursiontracker"); if (hud != null) { if ((Object)(object)hud.GameObject != (Object)null) { hud.Destroy(); } hud = null; } } public void Update() { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012b: 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_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) try { if (!isTracking || enableHud == null || !enableHud.Value) { if (IsHudAlive && hud.IsActive) { hud.SetActive(false); } return; } if (hud != null && !IsHudAlive) { ClearDestroyedHud(); } if (!isFrozen && (Object)(object)IncursionObjective.Instance != (Object)null) { int currentFloor = IncursionObjective.Instance.CurrentFloor; if (currentFloor != floor) { SetFloor(currentFloor); } } UpdateMissionTimerPauseState(); if (!((Object)(object)Player.LocalPlayer == (Object)null) && !((Object)(object)Player.LocalPlayer.PlayerLook == (Object)null) && !((Object)(object)Player.LocalPlayer.PlayerLook.Reticle == (Object)null)) { if (!IsHudAlive) { CreateTrackerHUD(); return; } float timeInSeconds = (isFrozen ? frozenMissionTime : GetCurrentMissionTime()); float timeInSeconds2 = (isFrozen ? frozenRemainingTime : remainingTime); Color val = (isFrozen ? frozenColor.Value : valueColor.Value); hud.Lines[0].SetRich("Mission", FormatTime(timeInSeconds), val, (string)null); hud.Lines[1].SetRich("Remaining", FormatRemaining(timeInSeconds2), val, (string)null); hud.Lines[2].SetRich("Floor", floor, val, (string)null); hud.Lines[3].SetRich("Aboms", abominationsKilled, val, (string)null); hud.Lines[4].SetRich("Levers", leversPulled, val, (string)null); hud.Lines[5].SetRich("Codes", codesInput, val, (string)null); } } catch (Exception ex) { SparrohPlugin.Logger.LogError((object)("Error in IncursionTrackerHUD.Update(): " + ex.Message)); } } private static string FormatTime(float timeInSeconds) { if (timeInSeconds < 0f) { timeInSeconds = 0f; } int num = (int)(timeInSeconds / 60f); int num2 = (int)(timeInSeconds % 60f); int num3 = (int)(timeInSeconds % 1f * 1000f); return $"{num:D2}:{num2:D2}.{num3:D3}"; } private static string FormatRemaining(float timeInSeconds) { if (timeInSeconds < 0f) { timeInSeconds = 0f; } int num = Mathf.FloorToInt(timeInSeconds / 60f); int num2 = Mathf.FloorToInt(timeInSeconds % 60f); return $"{num}:{num2:D2}"; } public void OnDestroy() { try { DestroyHud(); } catch (Exception ex) { SparrohPlugin.Logger.LogError((object)("Error in IncursionTrackerHUD.OnDestroy(): " + ex.Message)); } } } public class IncursionTrackerLoader { private IncursionTrackerHUD trackerHUD; private readonly ConfigFile configFile; private readonly Harmony harmony; public IncursionTrackerLoader(ConfigFile configFile, Harmony harmony) { this.configFile = configFile; this.harmony = harmony; trackerHUD = new IncursionTrackerHUD(configFile); } public void UpdateHudVisibility() { trackerHUD.UpdateHudVisibility(); } public void Update() { trackerHUD.Update(); } public void OnDestroy() { trackerHUD.OnDestroy(); } } [HarmonyPatch] public static class IncursionTrackerPatches { private static Action abominationSpawnedHandler; private static Action abominationKilledHandler; private static Action leverRepairedHandler; private static RepairableObject subscribedLever; private static float lastCodeInputRealtime = -999f; private const float CodeInputDebounceSeconds = 0.5f; private static void EnsureHandlers() { if (abominationSpawnedHandler == null) { abominationSpawnedHandler = OnAbominationSpawned; } if (abominationKilledHandler == null) { abominationKilledHandler = OnAbominationKilled; } if (leverRepairedHandler == null) { leverRepairedHandler = OnLeverRepaired; } } private static bool CanTrack() { if (IncursionTrackerHUD.Instance != null && IncursionTrackerHUD.Instance.IsTracking && !IncursionTrackerHUD.Instance.IsFrozen) { return (Object)(object)IncursionObjective.Instance != (Object)null; } return false; } private static void SubscribeAbominationTracking() { EnsureHandlers(); EnemyBrain.OnAbominationSpawned -= abominationSpawnedHandler; EnemyBrain.OnAbominationSpawned += abominationSpawnedHandler; } private static void UnsubscribeAbominationTracking() { if (abominationSpawnedHandler != null) { EnemyBrain.OnAbominationSpawned -= abominationSpawnedHandler; } UnsubscribeLever(); } private static void OnAbominationSpawned(EnemyBrain brain) { if (CanTrack() && !((Object)(object)brain == (Object)null)) { brain.OnKilled -= abominationKilledHandler; brain.OnKilled += abominationKilledHandler; } } private static void OnAbominationKilled(EnemyBrain brain) { if ((Object)(object)brain != (Object)null) { brain.OnKilled -= abominationKilledHandler; } if (CanTrack()) { IncursionTrackerHUD.Instance.OnAbominationKilled(); } } private static void SubscribeLever(RepairableObject lever) { EnsureHandlers(); UnsubscribeLever(); if (!((Object)(object)lever == (Object)null)) { subscribedLever = lever; subscribedLever.OnRepaired += leverRepairedHandler; } } private static void UnsubscribeLever() { if ((Object)(object)subscribedLever != (Object)null && leverRepairedHandler != null) { subscribedLever.OnRepaired -= leverRepairedHandler; } subscribedLever = null; } private static void OnLeverRepaired(RepairableObject lever) { if (CanTrack()) { IncursionTrackerHUD.Instance.OnLeverPulled(); } } [HarmonyPatch(typeof(IncursionObjective), "Setup")] [HarmonyPostfix] private static void Setup_Postfix(IncursionObjective __instance) { if (IncursionTrackerHUD.Instance == null) { return; } EnsureHandlers(); IncursionTrackerHUD.Instance.StartTracking(); SubscribeAbominationTracking(); try { FieldInfo fieldInfo = AccessTools.Field(typeof(IncursionObjective), "addTimeLever"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(__instance); RepairableObject val = (RepairableObject)((value is RepairableObject) ? value : null); if ((Object)(object)val != (Object)null) { SubscribeLever(val); } } } catch (Exception ex) { SparrohPlugin.Logger.LogWarning((object)("Could not subscribe to existing lever: " + ex.Message)); } } [HarmonyPatch(typeof(IncursionObjective), "OnDestroy")] [HarmonyPostfix] private static void OnDestroy_Postfix() { UnsubscribeAbominationTracking(); if (IncursionTrackerHUD.Instance != null) { IncursionTrackerHUD.Instance.StopTracking(); } } [HarmonyPatch(typeof(IncursionObjective), "SpawnRooms_ClientRpc")] [HarmonyPostfix] private static void SpawnRooms_ClientRpc_Postfix(IncursionObjective __instance, int currentFloor, int floorsReached) { if (IncursionTrackerHUD.Instance != null && IncursionTrackerHUD.Instance.IsTracking) { IncursionTrackerHUD.Instance.SetFloor(currentFloor); } } [HarmonyPatch(typeof(IncursionObjective), "OnCodeInputSuccess_ClientRpc")] [HarmonyPostfix] private static void OnCodeInputSuccess_ClientRpc_Postfix() { if (CanTrack()) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - lastCodeInputRealtime < 0.5f)) { lastCodeInputRealtime = realtimeSinceStartup; IncursionTrackerHUD.Instance.OnCodeInput(); } } } [HarmonyPatch(typeof(IncursionObjective), "OnAddTimeLeverSpawned_ClientRpc")] [HarmonyPostfix] private static void OnAddTimeLeverSpawned_ClientRpc_Postfix(NetworkBehaviourReference leverRef) { RepairableObject lever = default(RepairableObject); if (IncursionTrackerHUD.Instance != null && ((NetworkBehaviourReference)(ref leverRef)).TryGet(ref lever, (NetworkManager)null)) { SubscribeLever(lever); } } [HarmonyPatch(typeof(IncursionHUD), "UpdateTimer")] [HarmonyPostfix] private static void UpdateTimer_Postfix(float time) { if (IncursionTrackerHUD.Instance != null && IncursionTrackerHUD.Instance.IsTracking) { IncursionTrackerHUD.Instance.SetRemainingTime(time); } } [HarmonyPatch(typeof(IncursionHUD), "SetFloor")] [HarmonyPostfix] private static void SetFloor_Postfix(int floor) { if (IncursionTrackerHUD.Instance != null && IncursionTrackerHUD.Instance.IsTracking) { IncursionTrackerHUD.Instance.SetFloor(floor); } } } namespace IncursionTracker { public static class MyPluginInfo { public const string PLUGIN_GUID = "IncursionTracker"; public const string PLUGIN_NAME = "IncursionTracker"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }