using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [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("BountyHunters")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.1.0")] [assembly: AssemblyInformationalVersion("1.3.1+0d77aba4ef99a87d6be4daeb81b0de1f2e0c0dca")] [assembly: AssemblyProduct("BountyHunters")] [assembly: AssemblyTitle("BountyHunters")] [assembly: AssemblyVersion("1.3.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 BountyHunters { public static class BountySupportBridge { public static bool TryGetFinalShopTargetMoney(out int finalHaul) { if (!Plugin.ShouldRunClientLogic()) { finalHaul = 0; return false; } finalHaul = Plugin.PersistedShopTargetMoney; return finalHaul > 0; } } internal static class BountyBridge { public static bool HasOverlayExport => Plugin.ShouldRunClientLogic(); public static int GetOverlayLockedBountyTotal() { if (!HasOverlayExport) { return 0; } return BountyMoney.GetAccumulatedLockedBountyTotal(); } public static bool ShouldOverlayShowBounty() { if (HasOverlayExport) { return GetOverlayLockedBountyTotal() > 0; } return false; } public static int GetFinalShopTargetMoney() { if (!HasOverlayExport) { return 0; } return Plugin.PersistedShopTargetMoney; } } internal static class BountyEconomy { internal static int GetRepeatBountyCap() { float num = (float)Mathf.Clamp(Plugin.BountyCapPercent?.Value ?? 25, 25, 100) / 100f; return FloorToBountyStep(Mathf.Max(0, Mathf.RoundToInt((float)GetBaseExtractedHaul() * num))); } internal static int GetRemainingRepeatBountyCap() { return Mathf.Max(0, GetRepeatBountyCap() - Plugin.RepeatBountyEarnedTotal); } internal static int GetBaseExtractedHaul() { int num = Mathf.Max(0, Plugin.LevelStartHaulSnapshot); int currentHaulWithoutInjectedBounty = Plugin.GetCurrentHaulWithoutInjectedBounty(); return Mathf.Max(0, currentHaulWithoutInjectedBounty - num); } internal static int GetMissionTotalBountyCeiling() { return GetRepeatBountyCap(); } internal static int FloorToBountyStep(int amount) { if (amount <= 0) { return 0; } return amount / 1000 * 1000; } internal static bool CanAwardBounty() { return GetRemainingRepeatBountyCap() > 0; } internal static int GetAwardedBountyAmount(int baseRewardAmount) { if (baseRewardAmount <= 0) { return 0; } int remainingRepeatBountyCap = GetRemainingRepeatBountyCap(); if (remainingRepeatBountyCap <= 0) { return 0; } return FloorToBountyStep(Mathf.Min(baseRewardAmount, remainingRepeatBountyCap)); } internal static int GetTotalLockedBounty() { return Plugin.AccumulatedLockedBountyTotal; } internal static bool IsCapReached() { int missionTotalBountyCeiling = GetMissionTotalBountyCeiling(); if (missionTotalBountyCeiling > 0) { return GetTotalLockedBounty() >= missionTotalBountyCeiling; } return false; } internal static (int earned, int cap, bool isReached) GetCapProgress() { int totalLockedBounty = GetTotalLockedBounty(); int missionTotalBountyCeiling = GetMissionTotalBountyCeiling(); bool item = missionTotalBountyCeiling > 0 && totalLockedBounty >= missionTotalBountyCeiling; return (totalLockedBounty, missionTotalBountyCeiling, item); } internal static string GetEconomyStatus() { return $"cap={GetRepeatBountyCap():N0} | remaining={GetRemainingRepeatBountyCap():N0} | " + $"unique={Plugin.UniqueBountyEarnedTotal:N0} | repeat={Plugin.RepeatBountyEarnedTotal:N0} | " + $"total={GetTotalLockedBounty():N0} | haul={GetBaseExtractedHaul():N0}"; } } public static class BountyEvents { public static event Action? OnHaulUpdated; public static event Action? OnBountyPhaseChanged; public static event Action? OnEnemyKilled; public static event Action? OnBountyRewardGranted; public static event Action? OnBountyHuntComplete; internal static void RaiseHaulUpdated(int newHaulValue) { if (Plugin.VerboseDebugLogs) { Debug.Log((object)$"[BountyEvents] OnHaulUpdated: {newHaulValue}"); } BountyEvents.OnHaulUpdated?.Invoke(newHaulValue); } internal static void RaisePhaseChanged() { if (Plugin.VerboseDebugLogs) { Debug.Log((object)"[BountyEvents] OnBountyPhaseChanged"); } BountyEvents.OnBountyPhaseChanged?.Invoke(); } internal static void RaiseEnemyKilled(string enemyName) { if (Plugin.VerboseDebugLogs) { Debug.Log((object)("[BountyEvents] OnEnemyKilled: " + enemyName)); } BountyEvents.OnEnemyKilled?.Invoke(enemyName); } internal static void RaiseRewardGranted(int rewardAmount) { if (Plugin.VerboseDebugLogs) { Debug.Log((object)$"[BountyEvents] OnBountyRewardGranted: {rewardAmount}"); } BountyEvents.OnBountyRewardGranted?.Invoke(rewardAmount); } internal static void RaiseBountyHuntComplete() { if (Plugin.VerboseDebugLogs) { Debug.Log((object)"[BountyEvents] OnBountyHuntComplete"); } BountyEvents.OnBountyHuntComplete?.Invoke(); } } internal static class BountyKillFeedIntegration { private static bool? isKillFeedAvailable; private static bool isDeathTrackingConnected; internal static bool IsKillFeedAvailable { get { if (isKillFeedAvailable.HasValue) { return isKillFeedAvailable.Value; } try { Type type = Type.GetType("DenisUIKillFeed.Plugin, DenisUIKillFeed"); isKillFeedAvailable = type != null; return isKillFeedAvailable.Value; } catch { isKillFeedAvailable = false; return false; } } } internal static bool IsDeathTrackingConnected => isDeathTrackingConnected; internal static string GetDisplayName(string canonicalEnemyName) { if (string.IsNullOrWhiteSpace(canonicalEnemyName)) { return "Unknown"; } if (!IsKillFeedAvailable) { return canonicalEnemyName; } try { MethodInfo methodInfo = Type.GetType("DenisUIKillFeed.Plugin, DenisUIKillFeed")?.GetMethod("GetCustomEnemyName", BindingFlags.Static | BindingFlags.Public); if (methodInfo != null) { string text = methodInfo.Invoke(null, new object[1] { canonicalEnemyName }) as string; if (!string.IsNullOrEmpty(text) && text != "Unknown") { return text; } } } catch (Exception ex) { Plugin.DebugLog("[BountyHunters] Failed to get custom enemy name from Kill Feed: " + ex.Message); } return canonicalEnemyName; } internal static void TryConnectToDeathEvents() { if (isDeathTrackingConnected) { return; } try { Type type = Type.GetType("DenisUIKillFeed.EnemyDeathEvents, DenisUIKillFeed"); if (type == null) { Plugin.DebugLog("[BountyHunters] Kill Feed not installed - using independent enemy death tracking"); return; } PropertyInfo property = type.GetProperty("OnEnemyDeath", BindingFlags.Static | BindingFlags.Public); if (property == null) { Plugin.DebugLog("[BountyHunters] Kill Feed.OnEnemyDeath property not found - falling back to independent tracking"); return; } object value = property.GetValue(null); if (value == null) { Plugin.DebugLog("[BountyHunters] Kill Feed.OnEnemyDeath event is null - falling back to independent tracking"); return; } MethodInfo method = value.GetType().GetMethod("AddListener"); if (method == null) { Plugin.DebugLog("[BountyHunters] Could not find AddListener method - falling back to independent tracking"); return; } Action action = delegate(object enemy, object parent, string name) { OnEnemyDeathFromKillFeed(name); }; method.Invoke(value, new object[1] { action }); BountyTracking.IsKillFeedDeathTrackingActive = true; isDeathTrackingConnected = true; Plugin.DebugLog("[BountyHunters] ✅ Connected to Kill Feed death events"); } catch (Exception ex) { Plugin.DebugLog("[BountyHunters] Failed to connect to Kill Feed death events: " + ex.Message); Plugin.DebugLog("[BountyHunters] Falling back to independent enemy death tracking"); } } private static void OnEnemyDeathFromKillFeed(string normalizedEnemyName) { if (!string.IsNullOrWhiteSpace(normalizedEnemyName)) { Plugin.DebugLog("[BountyHunters] Enemy death event from Kill Feed: " + normalizedEnemyName); BountyTracking.OnEnemyDeathFromKillFeed(normalizedEnemyName); } } } internal sealed class EnemyViewState { public string Name = "Monster"; public bool Alive; public bool Spawned; public bool Dead; public int CurrentHp; public int LastHealth; public bool HadAliveHealthSample; public bool HadDamageEvidence; public bool WasDead; public bool DeathAnnounced; public bool EligibleForBounty = true; public bool BountyLocked; public bool RewardGrantedThisLife; } internal static class BountyMoney { internal static int GetRewardPreviewForEnemy(string enemyName) { if (!BountyRewardTable.TryGetReward(enemyName, out var reward) || reward <= 0) { return 0; } (int earned, int cap, bool isReached) capProgress = BountyEconomy.GetCapProgress(); int item = capProgress.earned; int val = capProgress.cap - item; return Math.Min(reward, Math.Max(0, val)); } internal static void SyncRunCurrencyToPersistedTarget(string source) { try { if (Plugin.PersistedShopTargetMoney <= 0) { Plugin.DebugLog("[BountyDebug] Run currency sync skipped | source=" + source + " | reason=no-persisted-target"); return; } int num = Mathf.RoundToInt((float)Plugin.PersistedShopTargetMoney / 1000f); if (Plugin.StatGetRunCurrencyMethod == null || Plugin.StatSetRunCurrencyMethod == null) { Plugin.DebugLog("[BountyDebug] Run currency sync unavailable | source=" + source + " | get=" + Plugin.DescribeMethod(Plugin.StatGetRunCurrencyMethod) + " | set=" + Plugin.DescribeMethod(Plugin.StatSetRunCurrencyMethod)); } else { object obj = Plugin.ResolveInvocationTarget(Plugin.StatGetRunCurrencyMethod); object obj2 = Plugin.ResolveInvocationTarget(Plugin.StatSetRunCurrencyMethod); int num2 = Convert.ToInt32(Plugin.StatGetRunCurrencyMethod.Invoke(obj, Array.Empty())); Plugin.StatSetRunCurrencyMethod.Invoke(obj2, new object[1] { num }); int num3 = Convert.ToInt32(Plugin.StatGetRunCurrencyMethod.Invoke(obj, Array.Empty())); Plugin.DebugLog($"[BountyDebug] Run currency sync applied | source={source} | persistedTarget={Plugin.PersistedShopTargetMoney:N0} | targetK={num:N0} | before={num2:N0} | after={num3:N0}"); } } catch (Exception ex) { Plugin.Logger.LogError((object)("[BountyDebug] Run currency sync failed | source=" + source + " | error=" + ex.Message)); } } internal static int GetAccumulatedLockedBountyTotal() { return Plugin.AccumulatedLockedBountyTotal; } internal static void InjectLockedBountyIntoFinalHaul(string source) { int accumulatedLockedBountyTotal = GetAccumulatedLockedBountyTotal(); int num = accumulatedLockedBountyTotal - Plugin.InjectedLockedBountyTotal; Plugin.DebugLog($"[BountyDebug] Haul injection attempt | source={source} | lockedBounty={accumulatedLockedBountyTotal:N0} | alreadyInjected={Plugin.InjectedLockedBountyTotal:N0} | deltaToInject={num:N0} | totalHaulNow={Plugin.ReadTotalHaul():N0}"); if ((Object)(object)RoundDirector.instance == (Object)null) { return; } if (num <= 0) { Plugin.DebugLog($"[BountyDebug] Haul injection skipped | source={source} | reason=no-new-bounty | lockedBounty={accumulatedLockedBountyTotal:N0} | alreadyInjected={Plugin.InjectedLockedBountyTotal:N0}"); return; } try { Traverse rd = Traverse.Create((object)RoundDirector.instance); float num2 = Plugin.ReadTotalHaul(); float num3 = Plugin.TryReadRoundDirectorFloat("currentHaul"); float num4 = Plugin.TryReadRoundDirectorFloat("extractionHaul"); Plugin.AddToRoundDirectorFloat(rd, "totalHaul", num); Plugin.AddToRoundDirectorFloat(rd, "currentHaul", num); Plugin.AddToRoundDirectorFloat(rd, "extractionHaul", num); float num5 = Plugin.ReadTotalHaul(); float num6 = Plugin.TryReadRoundDirectorFloat("currentHaul"); float num7 = Plugin.TryReadRoundDirectorFloat("extractionHaul"); Plugin.InjectedLockedBountyTotal += num; Plugin.PersistedShopLockedBountyTotal = accumulatedLockedBountyTotal; Plugin.PersistedShopInjectedBountyTotal = Plugin.InjectedLockedBountyTotal; Plugin.PersistedShopTargetMoney = Mathf.RoundToInt(num5); Plugin.ShopMoneyOverrideArmed = accumulatedLockedBountyTotal > 0; Plugin.DebugLog($"[BountyDebug] Haul injection applied | source={source} | deltaInjected={num:N0} | lockedBounty={accumulatedLockedBountyTotal:N0} | alreadyInjectedNow={Plugin.InjectedLockedBountyTotal:N0} | persistedShopLocked={Plugin.PersistedShopLockedBountyTotal:N0} | persistedShopInjected={Plugin.PersistedShopInjectedBountyTotal:N0} | persistedShopTargetMoney={Plugin.PersistedShopTargetMoney:N0} | totalHaul {num2:N0}->{num5:N0} | currentHaul {num3:N0}->{num6:N0} | extractionHaul {num4:N0}->{num7:N0}"); SyncRunCurrencyToPersistedTarget(source); } catch (Exception ex) { Plugin.Logger.LogError((object)("[BountyDebug] Haul injection failed | source=" + source + " | error=" + ex.Message)); } } internal static int GetShopAdjustedTotalHaul(int baseValue) { int num = ((Plugin.PersistedShopTargetMoney > 0) ? Plugin.PersistedShopTargetMoney : baseValue); Plugin.DebugLog($"[Shop] GetShopAdjustedTotalHaul | base={baseValue:N0} | persisted={Plugin.PersistedShopTargetMoney:N0} | armed={Plugin.ShopMoneyOverrideArmed} | result={num:N0}"); return num; } internal static int GetShopAdjustedCurrencyK(int baseValue) { int num = ((Plugin.PersistedShopTargetMoney > 0) ? Mathf.Max(0, Mathf.RoundToInt((float)Plugin.PersistedShopTargetMoney / 1000f)) : baseValue); Plugin.DebugLog($"[Shop] GetShopAdjustedCurrencyK | base={baseValue} | persisted={Plugin.PersistedShopTargetMoney:N0} | armed={Plugin.ShopMoneyOverrideArmed} | resultK={num}"); return num; } } [HarmonyPatch(typeof(RoundDirector))] internal static class BountyHuntersHudPatch { [HarmonyPatch("Update")] [HarmonyPostfix] private static void Update_Postfix() { if (Plugin.Enabled != null && !Plugin.Enabled.Value) { SetInactive("mod-disabled"); Plugin.ResetLevelState(); Plugin.ResetShopHandoffState(); return; } Plugin.SyncHostActivationBeacon(); if (!Plugin.ShouldRunClientLogic()) { SetInactive("client-logic-disabled"); Plugin.ResetLevelState(); Plugin.ResetShopHandoffState(); return; } if (Plugin.ShowBountyText != null && !Plugin.ShowBountyText.Value) { SetInactive("text-hidden-config"); } if (!SemiFunc.RunIsLevel()) { SetInactive("not-in-level"); Plugin.ResetLevelState(); Plugin.ResetShopHandoffState(); } else { if (!EnsureHud()) { return; } Plugin.EnsureLevelStartHaulSnapshot(); BountyTracking.MaybeLogSpawnTableEnemyNames(); if (Time.unscaledTime - Plugin.LastEnemyObserveAt >= 0.05f) { Plugin.LastEnemyObserveAt = Time.unscaledTime; BountyTracking.ObserveEnemies(); } bool num = Plugin.AllExtractionClosed(); Plugin.ApplyOrbSpawnPolicy(num); if (!num) { SetInactive("extraction-open"); Plugin.FinalPhaseEntered = false; return; } BountyTracking.ObserveFinalPhaseEconomy(); if (!Plugin.IsHaulPresentationReady()) { SetInactive("haul-not-ready"); } else { UpdateHud(); } } } private static bool EnsureHud() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("Game Hud"); GameObject val2 = GameObject.Find("Tax Haul"); TMP_Text val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent() : null); if ((Object)(object)val == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)val3.font == (Object)null) { return false; } if ((Object)(object)Plugin.Root == (Object)null) { Plugin.Root = new GameObject("Bounty Hunters UI"); Plugin.Root.transform.SetParent(val.transform, false); Plugin.Text = Plugin.Root.AddComponent(); ((TMP_Text)Plugin.Text).font = val3.font; ((Graphic)Plugin.Text).color = Color.white; Plugin.Root.SetActive(false); ((TMP_Text)Plugin.Text).fontSize = 16f; ((TMP_Text)Plugin.Text).enableWordWrapping = false; ((TMP_Text)Plugin.Text).alignment = (TextAlignmentOptions)257; ((TMP_Text)Plugin.Text).horizontalAlignment = (HorizontalAlignmentOptions)1; ((TMP_Text)Plugin.Text).verticalAlignment = (VerticalAlignmentOptions)256; ((Graphic)Plugin.Text).raycastTarget = false; ((TMP_Text)Plugin.Text).margin = Vector4.zero; ((TMP_Text)Plugin.Text).characterSpacing = 0f; ((TMP_Text)Plugin.Text).wordSpacing = 0f; ((TMP_Text)Plugin.Text).lineSpacing = 0f; ((TMP_Text)Plugin.Text).fontStyle = (FontStyles)0; RectTransform component = Plugin.Root.GetComponent(); component.pivot = new Vector2(0f, 1f); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(10f, -120f); component.sizeDelta = new Vector2(520f, 220f); } return true; } private static void UpdateHud() { //IL_04e0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.Text == (Object)null) { return; } float unscaledTime = Time.unscaledTime; float num = Mathf.Clamp(Plugin.UiScale?.Value ?? 1f, 0.7f, 1f); int num2 = Mathf.RoundToInt(20f * num); int rowSize = Mathf.RoundToInt(16f * num); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet3 = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (EnemyViewState value5 in Plugin.EnemyViews.Values) { if (value5.Alive && value5.EligibleForBounty) { hashSet.Add(value5.Name); } } foreach (KeyValuePair item in Plugin.StickyDisplayUntilByEnemyName) { if (item.Value > unscaledTime) { hashSet2.Add(item.Key); } } foreach (KeyValuePair item2 in Plugin.LockedBountyByEnemyName) { hashSet3.Add(item2.Key); } if (!Plugin.FinalPhaseEntered && hashSet.Count == 0 && hashSet3.Count == 0 && hashSet2.Count == 0) { SetInactive("no-alive-locked-sticky-before-final-phase"); } else { if (Plugin.FinalPhaseEntered && Plugin.FinalPhaseEnteredAt > -99999f && unscaledTime < Plugin.FinalPhaseEnteredAt + 0.2f && hashSet3.Count == 0 && hashSet2.Count == 0) { return; } string text = (IsNeutralMode() ? "#A8A8A8B8" : "#FE6000"); var (value, value2, flag) = BountyEconomy.GetCapProgress(); if (flag) { if (Plugin.ContractCompletedAt <= -99999f) { Plugin.ContractCompletedAt = unscaledTime; } } else { Plugin.ContractCompletedAt = -100000f; } string text2 = (flag ? "#7DFF7D" : text); float num3 = ((Plugin.ContractCompletedAt <= -99999f) ? 0f : (unscaledTime - Plugin.ContractCompletedAt)); if (flag && num3 >= 5.35f) { SetInactive("contract-complete-faded"); return; } List list = new List(); list.Add($"{FormatCompactMoney(value)} / {FormatCompactMoney(value2)} EARNED"); List list2 = list; foreach (KeyValuePair item3 in Plugin.LockedBountyByEnemyName) { int value3; int num4 = (Plugin.LastRenderedBountyByEnemyName.TryGetValue(item3.Key, out value3) ? value3 : 0); if (item3.Value > num4) { Plugin.MoneyFlashUntilByEnemyName[item3.Key] = unscaledTime + 0.26f; Plugin.MoneyBounceUntilByEnemyName[item3.Key] = unscaledTime + 0.24f; } Plugin.LastRenderedBountyByEnemyName[item3.Key] = item3.Value; } foreach (string enemySlot in Plugin.EnemySlots) { bool flag2 = hashSet.Contains(enemySlot); int value4; bool flag3 = Plugin.LockedBountyByEnemyName.TryGetValue(enemySlot, out value4) && value4 > 0; if (flag2 || flag3) { list2.Add(FormatMobLine(enemySlot, flag2)); } } if (list2.Count <= 1) { if (!((Object)(object)Plugin.Root != (Object)null) || !Plugin.Root.activeSelf || !(unscaledTime < Plugin.HudContentHoldUntil) || string.IsNullOrEmpty(Plugin.LastHudRenderedText)) { SetInactive($"no-renderable-lines | alive={hashSet.Count} | locked={hashSet3.Count} | sticky={hashSet2.Count}"); } return; } bool activeSelf = Plugin.Root.activeSelf; Plugin.Root.SetActive(true); if (!activeSelf) { Plugin.HudShowStartedAt = unscaledTime; } float alpha = 1f; if (flag && num3 > 5f) { float num5 = Mathf.Clamp01((num3 - 5f) / 0.35f); alpha = 1f - num5; } ((TMP_Text)Plugin.Text).alpha = alpha; float num6 = Mathf.Clamp01(Mathf.Max(0f, unscaledTime - Plugin.HudShowStartedAt) / 0.18f); float num7 = 1f - Mathf.Pow(1f - num6, 3f); float num8 = Mathf.Lerp(-28f, 0f, num7); Plugin.Root.GetComponent().anchoredPosition = new Vector2(10f + num8, -120f); string text3 = string.Join("\n", list2.Select((string line) => (!line.Contains("{line}" : line)); ((TMP_Text)Plugin.Text).text = text3; Plugin.LastHudRenderedText = text3; Plugin.HudContentHoldUntil = unscaledTime + 0.35f; } } private static int GetStableOrder(string enemyName) { if (!Plugin.StableOrderByEnemyName.TryGetValue(enemyName, out var value)) { return int.MaxValue; } return value; } private static bool IsNeutralMode() { if (Plugin.TextColor != null) { return Plugin.TextColor.Value == Plugin.TextColorMode.Neutral; } return false; } private static string FormatMobLine(string enemyName, bool isAlive) { Plugin.KillCountByEnemyName.TryGetValue(enemyName, out var value); Plugin.LockedBountyByEnemyName.TryGetValue(enemyName, out var value2); string displayName = BountyKillFeedIntegration.GetDisplayName(enemyName); bool num = IsNeutralMode(); string text = (num ? "#9C9C9CAA" : "#707070"); string text2 = (num ? "#FFFFFFCC" : "#ffffff"); string text3 = (num ? "#B0B0B099" : "#aaaaaa"); string text4 = ((value >= 2) ? $"x{value} " : string.Empty); if (value2 > 0) { float value3; bool flag = Plugin.MoneyFlashUntilByEnemyName.TryGetValue(enemyName, out value3) && Time.unscaledTime < value3; float value4; bool num2 = Plugin.MoneyBounceUntilByEnemyName.TryGetValue(enemyName, out value4) && Time.unscaledTime < value4; string text5 = (flag ? "#7DFF7D" : "#06FD06"); int num3 = (num2 ? 18 : 16); return $"{text4}{displayName} +${FormatCompactMoney(value2)}"; } if (isAlive && value == 0) { int rewardPreviewForEnemy = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy > 0) { return text4 + "" + displayName + " $" + FormatCompactMoney(rewardPreviewForEnemy) + ""; } } if (isAlive && value > 0) { int rewardPreviewForEnemy2 = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy2 > 0) { return text4 + "" + displayName + " +$" + FormatCompactMoney(value2) + " next $" + FormatCompactMoney(rewardPreviewForEnemy2) + ""; } } string text6 = (isAlive ? text2 : text3); return text4 + "" + displayName + ""; } private static string FormatCompactMoney(int value) { if (value >= 1000000) { float num = (float)value / 1000000f; if (!(num >= 10f) && !Mathf.Approximately(num, Mathf.Round(num))) { return $"{num:0.#}M"; } return $"{Mathf.RoundToInt(num)}M"; } if (value >= 1000) { float num2 = (float)value / 1000f; if (!(num2 >= 10f) && !Mathf.Approximately(num2, Mathf.Round(num2))) { return $"{num2:0.#}K"; } return $"{Mathf.RoundToInt(num2)}K"; } return value.ToString("N0"); } private static void SetInactive(string reason) { if (!((Object)(object)Plugin.Root == (Object)null)) { Plugin.Root.SetActive(false); } } } internal static class BountyRewardTable { internal static readonly Dictionary RewardsByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Apex Predator"] = 2000, ["Bella"] = 2000, ["Birthday Boy"] = 2000, ["Elsa"] = 2000, ["Peeper"] = 2000, ["Shadow Child"] = 2000, ["Spewer"] = 2000, ["Tick"] = 2000, ["Gnome"] = 0, ["Animal"] = 3000, ["Bowtie"] = 3000, ["Chef"] = 3000, ["Hidden"] = 3000, ["Mentalist"] = 3000, ["Rugrat"] = 3000, ["Upscream"] = 3000, ["Gambit"] = 3000, ["Heart Hugger"] = 3000, ["Oogly"] = 3000, ["Headgrab"] = 3000, ["Bang"] = 0, ["Cleanup Crew"] = 5000, ["Clown"] = 5000, ["Headman"] = 5000, ["Huntsman"] = 5000, ["Loom"] = 5000, ["Reaper"] = 5000, ["Robe"] = 5000, ["Trudge"] = 5000 }; internal static bool TryGetReward(string rawEnemyName, out int reward) { if (string.IsNullOrWhiteSpace(rawEnemyName)) { reward = 0; return false; } string text = EnemyNameDirectory.Normalize(rawEnemyName.Trim()); if (text == "Unknown") { reward = 0; return false; } return RewardsByEnemyName.TryGetValue(text, out reward); } } [HarmonyPatch(typeof(EnemyHealth))] internal static class BountyHuntersEnemyHealthPatch { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void Awake_Postfix(EnemyHealth __instance) { Plugin.RegisterEnemyHealthForOrbControl(__instance); Plugin.ApplyOrbSpawnPolicy(Plugin.AllExtractionClosed()); } } [HarmonyPatch(typeof(StatsManager))] internal static class BountyHuntersStatsManagerPatch { [HarmonyPatch("GetRunStatTotalHaul")] [HarmonyPostfix] private static void GetRunStatTotalHaul_Postfix(ref int __result) { try { int num = __result; bool shopMoneyOverrideArmed = Plugin.ShopMoneyOverrideArmed; if (shopMoneyOverrideArmed) { __result = BountyMoney.GetShopAdjustedTotalHaul(__result); } Plugin.DebugLog($"[BountyDebug] GetRunStatTotalHaul patch | armed={shopMoneyOverrideArmed} | in={num:N0} | out={__result:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0}"); } catch (Exception ex) { Plugin.Logger.LogError((object)("[BountyDebug] GetRunStatTotalHaul patch failed | error=" + ex.Message)); } } [HarmonyPatch("GetRunStatCurrency")] [HarmonyPostfix] private static void GetRunStatCurrency_Postfix(ref int __result) { try { if (Plugin.ShopMoneyOverrideArmed) { int num = __result; __result = BountyMoney.GetShopAdjustedCurrencyK(__result); Plugin.DebugLog($"[BountyDebug] GetRunStatCurrency patch | in={num:N0} | out={__result:N0}"); } } catch (Exception ex) { Plugin.Logger.LogError((object)("[BountyDebug] GetRunStatCurrency patch failed | error=" + ex.Message)); } } [HarmonyPatch("SaveFileGetTotalHaul")] [HarmonyPostfix] private static void SaveFileGetTotalHaul_Postfix(string folderName, string fileName, ref string __result) { try { bool shopMoneyOverrideArmed = Plugin.ShopMoneyOverrideArmed; string text = __result; if (!int.TryParse(__result, out var result)) { Plugin.DebugLog($"[BountyDebug] SaveFileGetTotalHaul patch | armed={shopMoneyOverrideArmed} | raw={text} | parseFailed=true | folder={folderName} | file={fileName} | persistedTarget={Plugin.PersistedShopTargetMoney:N0}"); return; } int num = (shopMoneyOverrideArmed ? BountyMoney.GetShopAdjustedTotalHaul(result) : result); if (shopMoneyOverrideArmed) { __result = num.ToString(); } Plugin.DebugLog($"[BountyDebug] SaveFileGetTotalHaul patch | armed={shopMoneyOverrideArmed} | in={result:N0} | out={num:N0} | folder={folderName} | file={fileName} | persistedTarget={Plugin.PersistedShopTargetMoney:N0}"); } catch (Exception ex) { Plugin.Logger.LogError((object)("[BountyDebug] SaveFileGetTotalHaul patch failed | error=" + ex.Message)); } } } [HarmonyPatch(typeof(ShopManager))] internal static class BountyHuntersShopManagerPatch { [HarmonyPatch("ShopInitialize")] [HarmonyPrefix] private static void ShopInitialize_Prefix() { Plugin.DebugLog($"[BountyDebug] ShopInitialize prefix | armed={Plugin.ShopMoneyOverrideArmed} | lockedBounty={BountyMoney.GetAccumulatedLockedBountyTotal():N0} | currentTotalHaul={Plugin.ReadTotalHaul():N0}"); } [HarmonyPatch("ShopInitialize")] [HarmonyPostfix] private static void ShopInitialize_Postfix(object __instance) { Plugin.DebugLog($"[BountyDebug] ShopInitialize postfix | armed={Plugin.ShopMoneyOverrideArmed} | persistedLocked={Plugin.PersistedShopLockedBountyTotal:N0} | persistedInjected={Plugin.PersistedShopInjectedBountyTotal:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0} | currentTotalHaul={Plugin.ReadTotalHaul():N0}"); } [HarmonyPatch("ShopCheck")] [HarmonyPostfix] private static void ShopCheck_Postfix(object __instance) { Plugin.DebugLog($"[BountyDebug] ShopCheck postfix | armed={Plugin.ShopMoneyOverrideArmed} | lockedBounty={BountyMoney.GetAccumulatedLockedBountyTotal():N0} | currentTotalHaul={Plugin.ReadTotalHaul():N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0}"); BountyMoney.SyncRunCurrencyToPersistedTarget("ShopCheck"); } [HarmonyPatch("GetAllItemsFromStatsManager")] [HarmonyPrefix] private static void GetAllItemsFromStatsManager_Prefix() { Plugin.DebugLog($"[BountyDebug] GetAllItemsFromStatsManager prefix | armed={Plugin.ShopMoneyOverrideArmed} | persistedLocked={Plugin.PersistedShopLockedBountyTotal:N0} | persistedInjected={Plugin.PersistedShopInjectedBountyTotal:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0} | currentTotalHaul={Plugin.ReadTotalHaul():N0}"); } [HarmonyPatch("GetAllItemsFromStatsManager")] [HarmonyPostfix] private static void GetAllItemsFromStatsManager_Postfix() { Plugin.DebugLog($"[BountyDebug] GetAllItemsFromStatsManager postfix | clearing persisted shop handoff | armed={Plugin.ShopMoneyOverrideArmed} | persistedLocked={Plugin.PersistedShopLockedBountyTotal:N0} | persistedInjected={Plugin.PersistedShopInjectedBountyTotal:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0}"); Plugin.ResetShopHandoffState(); } } internal static class BountyTracking { private static float lastFullCheckTime; private const float FULL_CHECK_INTERVAL = 0.15f; internal static bool IsKillFeedDeathTrackingActive { get; set; } private static bool ShouldRunFullCheck() { if (Time.time - lastFullCheckTime >= 0.15f) { lastFullCheckTime = Time.time; return true; } return false; } internal static void PopulateInitialEnemyPool() { if (Plugin.InitialEnemyPoolPopulated) { return; } EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null) { return; } foreach (EnemyParent item in instance.enemiesSpawned) { if ((Object)(object)item == (Object)null) { continue; } string text = (string.IsNullOrWhiteSpace(item.enemyName) ? "Monster" : item.enemyName); if (!Plugin.EnemySlots.Contains(text, StringComparer.OrdinalIgnoreCase)) { Plugin.EnemySlots.Add(text); if (!Plugin.StableOrderByEnemyName.ContainsKey(text)) { Plugin.StableOrderByEnemyName[text] = Plugin.StableOrderByEnemyName.Count; } } bool eligible = IsEligibleForBounty(text); TrackEncounteredEnemyType(text, eligible); } Plugin.InitialEnemyPoolPopulated = true; Plugin.DebugLog($"[BountyHunters] Populated initial enemy pool: {Plugin.EnemySlots.Count} enemy types"); } internal static void MaybeLogSpawnTableEnemyNames() { Plugin.LoggedSpawnTableEnemyNamesThisLevel = true; } internal static bool IsEligibleForBounty(string enemyName) { if (string.IsNullOrWhiteSpace(enemyName)) { return true; } return !Plugin.NoBountyEnemyNames.Contains(enemyName.Trim()); } internal static void TrackEncounteredEnemyType(string enemyName, bool eligible) { if (eligible && !string.IsNullOrWhiteSpace(enemyName) && BountyRewardTable.TryGetReward(enemyName, out var reward) && reward > 0) { Plugin.EncounteredRewardableEnemyTypes.Add(enemyName); } } internal static void ObserveEnemies() { //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Expected O, but got Unknown if (!ShouldRunFullCheck()) { return; } HashSet seen = new HashSet(); EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null) { return; } foreach (EnemyParent item in instance.enemiesSpawned) { if ((Object)(object)item == (Object)null) { continue; } int instanceID = ((Object)((Component)item).gameObject).GetInstanceID(); seen.Add(instanceID); string obj = (string.IsNullOrWhiteSpace(item.enemyName) ? "Monster" : item.enemyName); bool flag = IsEligibleForBounty(obj); TrackEncounteredEnemyType(obj, flag); string text = obj; Enemy enemyFromParent = Plugin.GetEnemyFromParent(item); EnemyHealth enemyHealth = Plugin.GetEnemyHealth(enemyFromParent); bool num = (Object)(object)enemyHealth != (Object)null && Plugin.GetEnemyHasHealth(enemyFromParent); bool parentSpawned = Plugin.GetParentSpawned(item); int num2 = (num ? Plugin.GetEnemyHealthCurrent(enemyHealth) : 0); bool flag2 = num && Plugin.GetEnemyHealthDead(enemyHealth); bool flag3 = parentSpawned && !flag2; if (!Plugin.StableOrderByEnemyName.ContainsKey(text)) { Plugin.StableOrderByEnemyName[text] = Plugin.StableOrderByEnemyName.Count; } if (!Plugin.EnemySlots.Contains(text, StringComparer.OrdinalIgnoreCase)) { Plugin.EnemySlots.Add(text); } if (Plugin.EnemyViews.TryGetValue(instanceID, out EnemyViewState value)) { if (num2 > 0 && !flag2) { value.HadAliveHealthSample = true; if (value.LastHealth > 0 && num2 < value.LastHealth) { value.HadDamageEvidence = true; } } bool flag4 = Plugin.FinalPhaseEntered && flag && !value.DeathAnnounced && value.HadAliveHealthSample && value.HadDamageEvidence && flag2 && !value.WasDead; if (flag2 && !value.WasDead) { Plugin.DebugLog($"[Death] {text} | final={Plugin.FinalPhaseEntered} | eligible={flag} | announced={value.DeathAnnounced} | aliveHad={value.HadAliveHealthSample} | damageHad={value.HadDamageEvidence} | confirmed={flag4} | killFeedActive={IsKillFeedDeathTrackingActive}"); } if (flag4 && !IsKillFeedDeathTrackingActive) { Plugin.DebugLog("[Death] " + text + " - Processing kill (Kill Feed inactive)"); RegisterKillEvent(text); value.DeathAnnounced = true; } if (flag3) { value.RewardGrantedThisLife = false; value.DeathAnnounced = false; } value.Name = text; value.Alive = flag3; value.Spawned = parentSpawned; value.Dead = flag2; value.WasDead = flag2; value.CurrentHp = num2; value.LastHealth = num2; value.EligibleForBounty = flag; value.BountyLocked = Plugin.LockedBountyByEnemyName.TryGetValue(text, out var value2) && value2 > 0; continue; } int value3; EnemyViewState newState = new EnemyViewState { Name = text, Alive = flag3, Spawned = parentSpawned, Dead = flag2, WasDead = flag2, CurrentHp = num2, LastHealth = num2, EligibleForBounty = flag, BountyLocked = (Plugin.LockedBountyByEnemyName.TryGetValue(text, out value3) && value3 > 0), RewardGrantedThisLife = false, HadAliveHealthSample = (num2 > 0 && !flag2), HadDamageEvidence = false, DeathAnnounced = false }; Plugin.EnemyViews[instanceID] = newState; try { if (!((Object)(object)enemyHealth != (Object)null) || !(Plugin.EnemyHealthOnDeathField != null)) { continue; } object? value4 = Plugin.EnemyHealthOnDeathField.GetValue(enemyHealth); UnityEvent val = (UnityEvent)((value4 is UnityEvent) ? value4 : null); if (val != null) { UnityAction val2 = (UnityAction)delegate { HandleEnemyDeathFromEvent(newState); }; val.AddListener(val2); } } catch (Exception ex) { Plugin.DebugLog("[BountyDebug] Failed to subscribe to enemy death event: " + ex.Message); } } foreach (int item2 in Plugin.EnemyViews.Keys.Where((int id) => !seen.Contains(id)).ToList()) { Plugin.EnemyViews.Remove(item2); } } private static void HandleEnemyDeathFromEvent(EnemyViewState state) { if (state != null && Plugin.FinalPhaseEntered && state.EligibleForBounty && (!state.DeathAnnounced || !state.Dead)) { Plugin.DebugLog("[Death] " + state.Name + " - Death event from UnityEvent (instant detection)"); RegisterKillEvent(state.Name); state.DeathAnnounced = true; } } internal static void OnEnemyDeathFromKillFeed(string normalizedEnemyName) { if (Plugin.FinalPhaseEntered && IsEligibleForBounty(normalizedEnemyName)) { RegisterKillEvent(normalizedEnemyName); } } internal static void RegisterKillEvent(string enemyName) { float unscaledTime = Time.unscaledTime; Plugin.StickyDisplayUntilByEnemyName[enemyName] = unscaledTime + 18f; if (!Plugin.StableOrderByEnemyName.ContainsKey(enemyName)) { Plugin.StableOrderByEnemyName[enemyName] = Plugin.StableOrderByEnemyName.Count; } if (!Plugin.EnemySlots.Contains(enemyName, StringComparer.OrdinalIgnoreCase)) { Plugin.EnemySlots.Add(enemyName); } if (BountyRewardTable.TryGetReward(enemyName, out var reward)) { bool flag = Plugin.ClaimedUniqueEnemyTypes.Add(enemyName); if (!BountyEconomy.CanAwardBounty()) { Plugin.DebugLog($"[Kill] enemy={enemyName} | status=capped | simple={reward:N0} | {BountyEconomy.GetEconomyStatus()}"); return; } int awardedBountyAmount = BountyEconomy.GetAwardedBountyAmount(reward); string text = (flag ? "unique" : "repeat"); if (awardedBountyAmount <= 0) { Plugin.DebugLog($"[Kill] enemy={enemyName} | status=zero-award | simple={reward:N0} | {BountyEconomy.GetEconomyStatus()}"); return; } GrantBountyReward(enemyName, awardedBountyAmount, unscaledTime, text == "unique"); int value; int num = (Plugin.KillCountByEnemyName.TryGetValue(enemyName, out value) ? value : 0); Plugin.DebugLog($"[Kill] enemy={enemyName} | awarded={awardedBountyAmount:N0} | type={text} | kills={num} | {BountyEconomy.GetEconomyStatus()}"); } else if (Plugin.LoggedSimpleRewardMissingNames.Add(enemyName)) { Plugin.Logger.LogWarning((object)("[BountyHunters] NO REWARD for enemy '" + enemyName + "' - check EnemyNameDirectory mapping")); } } internal static void GrantBountyReward(string enemyName, int reward, float now, bool uniqueClaim) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) string enemyName2 = enemyName; int num = Mathf.Max(0, reward); if (num > 0) { try { string text = FormatCompactMoney(num); SemiFunc.UIBigMessage("+" + text, "{moneyeyes}", 25f, Color.green, Color.green); } catch (Exception ex) { Plugin.DebugLog("[BountyDebug] Failed to show bounty popup: " + ex.Message); } } if (Plugin.LockedBountyByEnemyName.ContainsKey(enemyName2)) { Plugin.LockedBountyByEnemyName[enemyName2] += num; } else { Plugin.LockedBountyByEnemyName[enemyName2] = num; } foreach (EnemyViewState item in Plugin.EnemyViews.Values.Where((EnemyViewState x) => string.Equals(x.Name, enemyName2, StringComparison.OrdinalIgnoreCase))) { item.BountyLocked = true; } if (uniqueClaim) { Plugin.UniqueBountyEarnedTotal += num; } BountyEvents.RaiseEnemyKilled(enemyName2); if (num > 0) { BountyEvents.RaiseRewardGranted(num); } else { Plugin.RepeatBountyEarnedTotal += num; } if (Plugin.KillCountByEnemyName.ContainsKey(enemyName2)) { Plugin.KillCountByEnemyName[enemyName2]++; } else { Plugin.KillCountByEnemyName[enemyName2] = 1; } Plugin.AccumulatedLockedBountyTotal += num; Plugin.PersistedShopLockedBountyTotal = Plugin.AccumulatedLockedBountyTotal; Plugin.LastLockedBountyAt = now; Plugin.ShopMoneyOverrideArmed = true; Plugin.DebugLog($"[GrantBountyReward] Starting | enemy={enemyName2} | lockedAmount={num}"); if (num <= 0) { return; } if ((Object)(object)RoundDirector.instance == (Object)null) { Plugin.Logger.LogWarning((object)"[GrantBountyReward] RoundDirector.instance is NULL - cannot inject haul"); return; } try { float num2 = Plugin.ReadTotalHaul(); Plugin.DebugLog($"[GrantBountyReward] Before haul: {num2:N0}"); Traverse val = Traverse.Create((object)RoundDirector.instance); if (val == null) { Plugin.Logger.LogError((object)"[GrantBountyReward] Failed to create Traverse"); return; } Plugin.AddToRoundDirectorFloat(val, "totalHaul", num); Plugin.DebugLog($"[GrantBountyReward] Added to RoundDirector.totalHaul: +{num}"); Plugin.AddToRoundDirectorFloat(val, "currentHaul", num); Plugin.AddToRoundDirectorFloat(val, "extractionHaul", num); if (Plugin.StatSetRunCurrencyMethod != null) { try { int num3 = 0; if (Plugin.StatGetRunCurrencyMethod != null) { object obj = Plugin.ResolveInvocationTarget(Plugin.StatGetRunCurrencyMethod); object obj2 = Plugin.StatGetRunCurrencyMethod.Invoke(obj, Array.Empty()); num3 = ((obj2 != null) ? Convert.ToInt32(obj2) : 0); } int num4 = Mathf.RoundToInt((float)num / 1000f); int num5 = num3 + num4; object obj3 = Plugin.ResolveInvocationTarget(Plugin.StatSetRunCurrencyMethod); Plugin.StatSetRunCurrencyMethod.Invoke(obj3, new object[1] { num5 }); Plugin.DebugLog($"[GrantBountyReward] Added to Game Currency: +{num4}K (was {num3}K, now {num5}K)"); } catch (Exception ex2) { Plugin.Logger.LogError((object)("[GrantBountyReward] Failed to add to game currency: " + ex2.Message)); } } else { Plugin.Logger.LogWarning((object)"[GrantBountyReward] StatSetRunCurrencyMethod is NULL - cannot add to game currency!"); } float num6 = Plugin.ReadTotalHaul(); Plugin.DebugLog($"[GrantBountyReward] After haul: {num6:N0} (added {num6 - num2:N0})"); Plugin.InjectedLockedBountyTotal += num; Plugin.PersistedShopLockedBountyTotal = Plugin.AccumulatedLockedBountyTotal; Plugin.PersistedShopInjectedBountyTotal = Plugin.InjectedLockedBountyTotal; Plugin.PersistedShopTargetMoney = Mathf.RoundToInt(num6); Plugin.DebugLog($"[GrantBountyReward] Shop target money set to: {Plugin.PersistedShopTargetMoney:N0}"); BountyEvents.RaiseHaulUpdated((int)num6); } catch (Exception ex3) { Plugin.Logger.LogError((object)("[GrantBountyReward] Exception: " + ex3.Message + "\n" + ex3.StackTrace)); } } internal static void ObserveFinalPhaseEconomy() { float unscaledTime = Time.unscaledTime; if (!Plugin.FinalPhaseEntered) { Plugin.FinalPhaseEntered = true; Plugin.FinalPhaseEnteredAt = unscaledTime; Plugin.LastFinalPhaseLogTime = unscaledTime; Plugin.DebugLog("[BountyDebug] Final phase entered | simpleMode=true"); } else { if (unscaledTime - Plugin.LastFinalPhaseLogTime < 1.5f) { return; } int num = 0; foreach (EnemyViewState value in Plugin.EnemyViews.Values) { if (value != null && value.Alive && value.EligibleForBounty) { num++; } } Plugin.DebugLog("[BountyDebug] Tick | simpleMode=true | alive=" + num + " | locked=" + Plugin.LockedBountyByEnemyName.Count); Plugin.LastFinalPhaseLogTime = unscaledTime; } } private static string FormatCompactMoney(int value) { if (value >= 1000000) { float num = (float)value / 1000000f; if (!(num >= 10f) && !Mathf.Approximately(num, Mathf.Round(num))) { return $"{num:0.#}M"; } return $"{Mathf.RoundToInt(num)}M"; } if (value >= 1000) { float num2 = (float)value / 1000f; if (!(num2 >= 10f) && !Mathf.Approximately(num2, Mathf.Round(num2))) { return $"{num2:0.#}K"; } return $"{Mathf.RoundToInt(num2)}K"; } return value.ToString("N0"); } } internal static class EnemyNameDirectory { public static readonly Dictionary NameMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Animal", "Animal" }, { "EnemyAnimal", "Animal" }, { "Bang", "Bang" }, { "EnemyBang", "Bang" }, { "Beamer", "Clown" }, { "EnemyBeamer", "Clown" }, { "Clown", "Clown" }, { "BirthdayBoy", "Birthday Boy" }, { "EnemyBirthdayBoy", "Birthday Boy" }, { "Birthday Boy", "Birthday Boy" }, { "BombThrower", "Cleanup Crew" }, { "Bomb Thrower", "Cleanup Crew" }, { "EnemyBombThrower", "Cleanup Crew" }, { "Cleanup Crew", "Cleanup Crew" }, { "Bowtie", "Bowtie" }, { "EnemyBowtie", "Bowtie" }, { "CeilingEye", "Peeper" }, { "Ceiling Eye", "Peeper" }, { "EnemyCeilingEye", "Peeper" }, { "Peeper", "Peeper" }, { "Duck", "Apex Predator" }, { "EnemyDuck", "Apex Predator" }, { "Apex Predator", "Apex Predator" }, { "Elsa", "Elsa" }, { "EnemyElsa", "Elsa" }, { "Floater", "Mentalist" }, { "EnemyFloater", "Mentalist" }, { "Mentalist", "Mentalist" }, { "Gnome", "Gnome" }, { "EnemyGnome", "Gnome" }, { "HeadController", "Headman" }, { "Head", "Headman" }, { "EnemyHeadController", "Headman" }, { "Headman", "Headman" }, { "HeadGrabber", "Headgrab" }, { "Head Grabber", "Headgrab" }, { "EnemyHeadGrabber", "Headgrab" }, { "Headgrab", "Headgrab" }, { "HeartHugger", "Heart Hugger" }, { "EnemyHeartHugger", "Heart Hugger" }, { "Hidden", "Hidden" }, { "EnemyHidden", "Hidden" }, { "Hunter", "Huntsman" }, { "EnemyHunter", "Huntsman" }, { "Huntsman", "Huntsman" }, { "Oogly", "Oogly" }, { "EnemyOogly", "Oogly" }, { "Robe", "Robe" }, { "EnemyRobe", "Robe" }, { "Runner", "Reaper" }, { "EnemyRunner", "Reaper" }, { "Reaper", "Reaper" }, { "Shadow", "Loom" }, { "EnemyShadow", "Loom" }, { "Loom", "Loom" }, { "SlowMouth", "Spewer" }, { "Slow Mouth", "Spewer" }, { "EnemySlowMouth", "Spewer" }, { "Spewer", "Spewer" }, { "SlowWalker", "Trudge" }, { "Slow Walker", "Trudge" }, { "EnemySlowWalker", "Trudge" }, { "Trudge", "Trudge" }, { "Spinny", "Gambit" }, { "EnemySpinny", "Gambit" }, { "Gambit", "Gambit" }, { "ThinMan", "Shadow Child" }, { "Thin Man", "Shadow Child" }, { "EnemyThinMan", "Shadow Child" }, { "Shadow Child", "Shadow Child" }, { "Tick", "Tick" }, { "EnemyTick", "Tick" }, { "Tricycle", "Bella" }, { "EnemyTricycle", "Bella" }, { "Bella", "Bella" }, { "Tumbler", "Chef" }, { "EnemyTumbler", "Chef" }, { "Chef", "Chef" }, { "Upscream", "Upscream" }, { "EnemyUpscream", "Upscream" }, { "ValuableThrower", "Rugrat" }, { "Valuable Thrower", "Rugrat" }, { "EnemyValuableThrower", "Rugrat" }, { "Rugrat", "Rugrat" } }; public static string Normalize(string rawName) { if (string.IsNullOrWhiteSpace(rawName)) { return "Unknown"; } if (NameMapping.TryGetValue(rawName, out string value)) { return value; } Debug.LogWarning((object)("[BountyHunters] Unknown enemy name: '" + rawName + "' → Unknown")); return "Unknown"; } public static IEnumerable GetAllDisplayNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string value in NameMapping.Values) { hashSet.Add(value); } List list = new List(hashSet); list.Sort(); return list; } } [BepInPlugin("denis.repo.bountyhuntersui", "Bounty Hunters", "1.3.1")] public class Plugin : BaseUnityPlugin { internal enum TextColorMode { Orange, Neutral } internal static GameObject? Root; internal static TextMeshProUGUI? Text; internal static ConfigEntry Enabled = null; internal static ConfigEntry ShowBountyText = null; internal static ConfigEntry BountyCapPercent = null; internal static ConfigEntry UiScale = null; internal static ConfigEntry TextColor = null; internal static readonly Dictionary EnemyViews = new Dictionary(); internal static readonly Dictionary LockedBountyByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary KillCountByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet EncounteredRewardableEnemyTypes = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet ClaimedUniqueEnemyTypes = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary LastRenderedBountyByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary MoneyFlashUntilByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary MoneyBounceUntilByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet LoggedSimpleRewardMissingNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet LoggedHudPreviewRewardNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet LoggedHudPreviewMissingNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static float HudShowStartedAt = -100000f; internal static float HudContentHoldUntil = -100000f; internal static float ContractCompletedAt = -100000f; internal static string LastHudRenderedText = string.Empty; internal static readonly Dictionary StickyDisplayUntilByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary StableOrderByEnemyName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly List EnemySlots = new List(); internal static readonly Dictionary TrackedEnemyHealths = new Dictionary(); internal static readonly Dictionary OriginalSpawnValuableMaxByEnemyHealthId = new Dictionary(); internal static bool OrbSpawnsSuppressedAfterExtraction; internal static bool LoggedSpawnTableEnemyNamesThisLevel; internal static bool InitialEnemyPoolPopulated; internal static float LastEnemyObserveAt = -100000f; internal static bool OrbPolicyDirty = true; internal static int LastAppliedOrbPolicyMode = -1; internal static readonly FieldInfo? EnemyParentEnemyField = typeof(EnemyParent).GetField("Enemy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyParent).GetField("enemy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyParentSpawnedField = typeof(EnemyParent).GetField("Spawned", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyParent).GetField("spawned", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyHasHealthField = typeof(Enemy).GetField("HasHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(Enemy).GetField("hasHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyHealthRefField = typeof(Enemy).GetField("Health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(Enemy).GetField("health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyHealthCurrentField = typeof(EnemyHealth).GetField("healthCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyHealth).GetField("current", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyHealth).GetField("Health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyHealthDeadField = typeof(EnemyHealth).GetField("dead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyHealth).GetField("Dead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyHealth).GetField("isDead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly FieldInfo? EnemyHealthOnDeathField = typeof(EnemyHealth).GetField("onDeath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(EnemyHealth).GetField("OnDeath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static readonly HashSet NoBountyEnemyNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "Gnome", "Banger", "Tick" }; internal static float LastFinalPhaseLogTime = -100000f; internal static bool FinalPhaseEntered; internal static float FinalPhaseEnteredAt = -100000f; internal static int UniqueBountyEarnedTotal; internal static int RepeatBountyEarnedTotal; internal static int AccumulatedLockedBountyTotal; internal static int LevelStartHaulSnapshot = -1; internal static int InjectedLockedBountyTotal; internal static float LastLockedBountyAt; internal static bool ShopMoneyOverrideArmed; internal static int PersistedShopLockedBountyTotal; internal static int PersistedShopInjectedBountyTotal; internal static int PersistedShopTargetMoney; internal static MethodInfo? StatGetRunCurrencyMethod; internal static MethodInfo? StatSetRunCurrencyMethod; internal const string HostActiveRoomPropertyKey = "denis.bh.active"; internal static float LastHostBeaconSentAt = -100000f; internal static readonly bool VerboseDebugLogs = false; internal const float StickyDisplaySeconds = 18f; internal const float MoneyFlashSeconds = 0.26f; internal const float MoneyBounceSeconds = 0.24f; internal const float HudSlideSeconds = 0.18f; internal const float ContractCompleteHoldSeconds = 5f; internal const float ContractCompleteFadeSeconds = 0.35f; internal const float InitialHudGatherDelaySeconds = 0.2f; internal const float RowStaggerSeconds = 0.04f; internal const float EnemyObserveIntervalSeconds = 0.05f; internal static Plugin Instance { get; private set; } = null; internal Harmony Harmony { get; private set; } internal static ManualLogSource Logger => Instance._logger; private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger; private void Awake() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown Instance = this; ((Component)this).transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Bounty Hunters", true, "Globally enables or disables the Bounty Hunters mod."); ShowBountyText = ((BaseUnityPlugin)this).Config.Bind("General", "Snow Text", true, "Shows or hides the Bounty Hunters UI text without disabling bounty logic."); BountyCapPercent = ((BaseUnityPlugin)this).Config.Bind("General", "% Reward Cap", 40, new ConfigDescription("Limits all bounty payouts to this percent of haul earned during the current level. Min 25% (can revert if too generous).", (AcceptableValueBase)(object)new AcceptableValueRange(25, 100), Array.Empty())); TextColor = ((BaseUnityPlugin)this).Config.Bind("General", "Text Color", TextColorMode.Orange, "Controls the Bounty Hunters header/body accent style."); UiScale = ((BaseUnityPlugin)this).Config.Bind("General", "Text Scale", 1f, new ConfigDescription("Scales Bounty Hunters text. 1.0 = maximum size baseline.", (AcceptableValueBase)(object)new AcceptableValueRange(0.7f, 1f), Array.Empty())); Harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony.PatchAll(); DiscoverRunCurrencyMethods(); BountyKillFeedIntegration.TryConnectToDeathEvents(); Logger.LogInfo((object)"Bounty Hunters loaded"); } private void OnDestroy() { Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static void ResetLevelState() { RestoreTrackedOrbSpawnValues(clearTracking: true); EnemyViews.Clear(); LockedBountyByEnemyName.Clear(); KillCountByEnemyName.Clear(); EncounteredRewardableEnemyTypes.Clear(); ClaimedUniqueEnemyTypes.Clear(); LastRenderedBountyByEnemyName.Clear(); MoneyFlashUntilByEnemyName.Clear(); MoneyBounceUntilByEnemyName.Clear(); LoggedSimpleRewardMissingNames.Clear(); LoggedHudPreviewRewardNames.Clear(); LoggedHudPreviewMissingNames.Clear(); StickyDisplayUntilByEnemyName.Clear(); StableOrderByEnemyName.Clear(); EnemySlots.Clear(); LastFinalPhaseLogTime = -100000f; FinalPhaseEntered = false; FinalPhaseEnteredAt = -100000f; UniqueBountyEarnedTotal = 0; RepeatBountyEarnedTotal = 0; AccumulatedLockedBountyTotal = 0; LevelStartHaulSnapshot = -1; InjectedLockedBountyTotal = 0; LastLockedBountyAt = -100000f; HudShowStartedAt = -100000f; HudContentHoldUntil = -100000f; ContractCompletedAt = -100000f; LastHudRenderedText = string.Empty; LoggedSpawnTableEnemyNamesThisLevel = false; InitialEnemyPoolPopulated = false; LastEnemyObserveAt = -100000f; OrbPolicyDirty = true; LastAppliedOrbPolicyMode = -1; } internal static void ResetShopHandoffState() { ShopMoneyOverrideArmed = false; PersistedShopLockedBountyTotal = 0; PersistedShopInjectedBountyTotal = 0; PersistedShopTargetMoney = 0; } internal static bool IsNetworkHost() { try { return PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient; } catch { return false; } } internal static bool HasHostActivationSignal() { try { if (!PhotonNetwork.InRoom) { return true; } if (PhotonNetwork.IsMasterClient) { return true; } Room currentRoom = PhotonNetwork.CurrentRoom; if (((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null) == null) { return false; } if (!((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"denis.bh.active", out object value) || value == null) { return false; } bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } internal static bool ShouldRunClientLogic() { if (Enabled != null && !Enabled.Value) { return false; } try { if (!PhotonNetwork.InRoom) { return true; } if (PhotonNetwork.IsMasterClient) { return true; } } catch { return false; } return HasHostActivationSignal(); } internal static bool ShouldApplyHostGameplayMutations() { if (Enabled != null && !Enabled.Value) { return false; } try { return !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient; } catch { return false; } } internal static void RegisterEnemyHealthForOrbControl(EnemyHealth health) { try { if (!((Object)(object)health == (Object)null)) { int instanceID = ((Object)((Component)health).gameObject).GetInstanceID(); TrackedEnemyHealths[instanceID] = health; if (!OriginalSpawnValuableMaxByEnemyHealthId.ContainsKey(instanceID)) { OriginalSpawnValuableMaxByEnemyHealthId[instanceID] = health.spawnValuableMax; } OrbPolicyDirty = true; } } catch { } } internal static void ApplyOrbSpawnPolicy(bool extractionClosed) { bool flag = ShouldApplyHostGameplayMutations(); bool flag2 = flag && extractionClosed; int num = (flag ? (flag2 ? 1 : 2) : 0); if (!OrbPolicyDirty && num == LastAppliedOrbPolicyMode) { OrbSpawnsSuppressedAfterExtraction = flag2; return; } List list = new List(); foreach (KeyValuePair trackedEnemyHealth in TrackedEnemyHealths) { EnemyHealth value = trackedEnemyHealth.Value; if ((Object)(object)value == (Object)null) { list.Add(trackedEnemyHealth.Key); continue; } int value2; int num2 = (OriginalSpawnValuableMaxByEnemyHealthId.TryGetValue(trackedEnemyHealth.Key, out value2) ? value2 : value.spawnValuableMax); if (!OriginalSpawnValuableMaxByEnemyHealthId.ContainsKey(trackedEnemyHealth.Key)) { OriginalSpawnValuableMaxByEnemyHealthId[trackedEnemyHealth.Key] = num2; } value.spawnValuableMax = ((!flag) ? num2 : ((!flag2) ? int.MaxValue : 0)); } foreach (int item in list) { TrackedEnemyHealths.Remove(item); OriginalSpawnValuableMaxByEnemyHealthId.Remove(item); } OrbSpawnsSuppressedAfterExtraction = flag2; OrbPolicyDirty = false; LastAppliedOrbPolicyMode = num; } internal static void RestoreTrackedOrbSpawnValues(bool clearTracking) { foreach (KeyValuePair trackedEnemyHealth in TrackedEnemyHealths) { if (!((Object)(object)trackedEnemyHealth.Value == (Object)null) && OriginalSpawnValuableMaxByEnemyHealthId.TryGetValue(trackedEnemyHealth.Key, out var value)) { trackedEnemyHealth.Value.spawnValuableMax = value; } } OrbSpawnsSuppressedAfterExtraction = false; OrbPolicyDirty = true; LastAppliedOrbPolicyMode = -1; if (clearTracking) { TrackedEnemyHealths.Clear(); OriginalSpawnValuableMaxByEnemyHealthId.Clear(); } } internal static void SyncHostActivationBeacon() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown try { if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient) { return; } float unscaledTime = Time.unscaledTime; if (!(unscaledTime - LastHostBeaconSentAt < 1.5f)) { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null) { Hashtable val = new Hashtable { [(object)"denis.bh.active"] = Enabled != null && Enabled.Value }; currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); LastHostBeaconSentAt = unscaledTime; } } } catch { } } internal static void DiscoverRunCurrencyMethods() { try { Type[] types = typeof(StatsManager).Assembly.GetTypes(); for (int i = 0; i < types.Length; i++) { MethodInfo[] methods = types[i].GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (StatGetRunCurrencyMethod == null && string.Equals(methodInfo.Name, "StatGetRunCurrency", StringComparison.OrdinalIgnoreCase)) { StatGetRunCurrencyMethod = methodInfo; } if (StatSetRunCurrencyMethod == null && string.Equals(methodInfo.Name, "StatSetRunCurrency", StringComparison.OrdinalIgnoreCase)) { StatSetRunCurrencyMethod = methodInfo; } } } DebugLog("[BountyDebug] Run currency methods discovered | get=" + DescribeMethod(StatGetRunCurrencyMethod) + " | set=" + DescribeMethod(StatSetRunCurrencyMethod)); } catch (Exception ex) { Logger.LogError((object)("[BountyDebug] Run currency method discovery failed | error=" + ex.Message)); } } internal static void DebugLog(string message) { if (VerboseDebugLogs) { Logger.LogInfo((object)message); } } internal static string DescribeMethod(MethodInfo? method) { if (method == null) { return "missing"; } string text = string.Join(", ", from p in method.GetParameters() select p.ParameterType.Name + " " + p.Name); return method.DeclaringType?.Name + "." + method.Name + "(" + text + ") -> " + method.ReturnType.Name; } internal static object? ResolveInvocationTarget(MethodInfo method) { if (method.IsStatic) { return null; } Type declaringType = method.DeclaringType; PropertyInfo propertyInfo = AccessTools.Property(declaringType, "instance") ?? AccessTools.Property(declaringType, "Instance"); if (propertyInfo != null) { return propertyInfo.GetValue(null, null); } FieldInfo fieldInfo = AccessTools.Field(declaringType, "instance") ?? AccessTools.Field(declaringType, "Instance"); if (fieldInfo != null) { return fieldInfo.GetValue(null); } return null; } internal static Enemy? GetEnemyFromParent(EnemyParent enemyParent) { try { object? obj = EnemyParentEnemyField?.GetValue(enemyParent); return (Enemy?)((obj is Enemy) ? obj : null); } catch { return null; } } internal static bool GetParentSpawned(EnemyParent enemyParent) { try { object obj = EnemyParentSpawnedField?.GetValue(enemyParent); if (obj is bool) { return (bool)obj; } } catch { } return true; } internal static bool GetEnemyHasHealth(Enemy? enemy) { try { if ((Object)(object)enemy == (Object)null) { return false; } object obj = EnemyHasHealthField?.GetValue(enemy); if (obj is bool) { return (bool)obj; } } catch { } return false; } internal static EnemyHealth? GetEnemyHealth(Enemy? enemy) { try { object? obj = EnemyHealthRefField?.GetValue(enemy); return (EnemyHealth?)((obj is EnemyHealth) ? obj : null); } catch { return null; } } internal static int GetEnemyHealthCurrent(EnemyHealth? health) { try { if ((Object)(object)health == (Object)null) { return 0; } object obj = EnemyHealthCurrentField?.GetValue(health); if (obj is int) { return (int)obj; } } catch { } return 0; } internal static bool GetEnemyHealthDead(EnemyHealth? health) { try { if ((Object)(object)health == (Object)null) { return false; } object obj = EnemyHealthDeadField?.GetValue(health); if (obj is bool) { return (bool)obj; } } catch { } return false; } internal static bool AllExtractionClosed() { if ((Object)(object)RoundDirector.instance == (Object)null) { return false; } try { return Traverse.Create((object)RoundDirector.instance).Field("allExtractionPointsCompleted").GetValue(); } catch { return false; } } internal static float ReadTotalHaul() { if ((Object)(object)RoundDirector.instance == (Object)null) { return 0f; } try { object value = Traverse.Create((object)RoundDirector.instance).Field("totalHaul").GetValue(); if (value is int num) { return num; } if (value is float result) { return result; } if (value is double num2) { return (float)num2; } if (value is IConvertible value2) { return Convert.ToSingle(value2); } } catch { } return 0f; } internal static int GetCurrentHaulWithoutInjectedBounty() { return Mathf.Max(0, Mathf.RoundToInt(ReadTotalHaul()) - InjectedLockedBountyTotal); } internal static int ReadRunCurrencyMoney() { try { if (StatGetRunCurrencyMethod == null) { return 0; } object obj = ResolveInvocationTarget(StatGetRunCurrencyMethod); object obj2 = StatGetRunCurrencyMethod.Invoke(obj, Array.Empty()); if (obj2 == null) { return 0; } int num = Convert.ToInt32(obj2); return Mathf.Max(0, num * 1000); } catch { return 0; } } internal static bool IsHaulPresentationReady() { if (PersistedShopTargetMoney > 0) { return true; } if (!AllExtractionClosed()) { return false; } return ReadRunCurrencyMoney() > 0; } internal static void EnsureLevelStartHaulSnapshot() { if (LevelStartHaulSnapshot < 0) { LevelStartHaulSnapshot = GetCurrentHaulWithoutInjectedBounty(); } } internal static int GetEncounteredUniqueBountyPool() { int num = 0; foreach (string encounteredRewardableEnemyType in EncounteredRewardableEnemyTypes) { if (BountyRewardTable.TryGetReward(encounteredRewardableEnemyType, out var reward) && reward > 0) { num += reward; } } return num; } internal static float TryReadRoundDirectorFloat(string fieldName) { if ((Object)(object)RoundDirector.instance == (Object)null) { return 0f; } try { if (TryConvertToFloat(Traverse.Create((object)RoundDirector.instance).Field(fieldName).GetValue(), out var result)) { return result; } } catch { } return 0f; } internal static void AddToRoundDirectorFloat(Traverse rd, string fieldName, float amount) { object value = rd.Field(fieldName).GetValue(); if (!TryConvertToFloat(value, out var result)) { result = 0f; } if (value is int) { rd.Field(fieldName).SetValue((object)Mathf.RoundToInt(result + amount)); } else if (value is double) { rd.Field(fieldName).SetValue((object)(double)(result + amount)); } else { rd.Field(fieldName).SetValue((object)(result + amount)); } } internal static bool TryConvertToFloat(object? value, out float result) { if (value == null) { result = 0f; return false; } if (value is float num) { result = num; return true; } if (value is int num2) { result = num2; return true; } if (value is double num3) { result = (float)num3; return true; } if (value is long num4) { result = num4; return true; } if (value is IConvertible value2) { try { result = Convert.ToSingle(value2); return true; } catch { result = 0f; return false; } } result = 0f; return false; } } }