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.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.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0d77aba4ef99a87d6be4daeb81b0de1f2e0c0dca")] [assembly: AssemblyProduct("BountyHunters")] [assembly: AssemblyTitle("BountyHunters")] [assembly: AssemblyVersion("1.0.0.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 { 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 sealed class EnemyViewState { public string Name = "Monster"; public bool Alive; public bool Spawned; public bool Dead; public int CurrentHp; public bool EligibleForBounty = true; public bool BountyLocked; public bool RewardGrantedThisLife; } internal static class BountyMoney { internal static int GetRemainingRepeatBountyCap() { return Mathf.Max(0, Plugin.GetRepeatBountyCap() - Plugin.RepeatBountyEarnedTotal); } internal static int GetRewardPreviewForEnemy(string enemyName) { if (!BountyRewardTable.TryGetReward(enemyName, out var reward) || reward <= 0) { return 0; } if (!Plugin.ClaimedUniqueEnemyTypes.Contains(enemyName)) { return reward; } return Plugin.FloorToBountyStep(Mathf.Min(reward, GetRemainingRepeatBountyCap())); } 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.LockedBountyByEnemyName.Values.Sum(); } 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($"[BountyDebug] Shop haul override query | armed={Plugin.ShopMoneyOverrideArmed} | base={baseValue:N0} | persistedLocked={Plugin.PersistedShopLockedBountyTotal:N0} | persistedInjected={Plugin.PersistedShopInjectedBountyTotal:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0} | adjusted={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($"[BountyDebug] Shop currency override query | armed={Plugin.ShopMoneyOverrideArmed} | base={baseValue:N0} | persistedTarget={Plugin.PersistedShopTargetMoney:N0} | adjustedK={num:N0}"); 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(); MaybeInjectLate(); if (!Plugin.IsHaulPresentationReady()) { SetInactive("haul-not-ready"); } else { UpdateHud(); } } } private static void MaybeInjectLate() { if (BountyMoney.GetAccumulatedLockedBountyTotal() > Plugin.InjectedLockedBountyTotal && !(Time.unscaledTime - Plugin.LastLockedBountyAt < 0.35f)) { BountyMoney.InjectLockedBountyIntoFinalHaul("RoundDirector.Update:auto-final-phase"); } } 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_06eb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.Text == (Object)null) { return; } float now = 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); List list = (from g in Plugin.EnemyViews.Values.Where((EnemyViewState x) => x.Alive && x.EligibleForBounty).GroupBy((EnemyViewState x) => x.Name, StringComparer.OrdinalIgnoreCase) select g.First() into x orderby GetStableOrder(x.Name), x.Name select x).ToList(); List list2 = (from x in Plugin.StickyDisplayUntilByEnemyName where x.Value > now select x.Key into x orderby GetStableOrder(x), x select x).ToList(); List> list3 = (from x in Plugin.LockedBountyByEnemyName orderby GetStableOrder(x.Key), x.Key select x).ToList(); if (!Plugin.FinalPhaseEntered && list.Count == 0 && list3.Count == 0 && list2.Count == 0) { SetInactive("no-alive-locked-sticky-before-final-phase"); } else { if (Plugin.FinalPhaseEntered && Plugin.FinalPhaseEnteredAt > -99999f && now < Plugin.FinalPhaseEnteredAt + 0.2f && list3.Count == 0 && list2.Count == 0) { return; } string text = (IsNeutralMode() ? "#A8A8A8B8" : "#FE6000"); int accumulatedLockedBountyTotal = BountyMoney.GetAccumulatedLockedBountyTotal(); int missionTotalBountyCeiling = Plugin.GetMissionTotalBountyCeiling(); Plugin.GetRepeatBountyCap(); bool flag = missionTotalBountyCeiling > 0 && accumulatedLockedBountyTotal >= missionTotalBountyCeiling; if (flag) { if (Plugin.ContractCompletedAt <= -99999f) { Plugin.ContractCompletedAt = now; } } else { Plugin.ContractCompletedAt = -100000f; } string text2 = (flag ? "#7DFF7D" : text); float num3 = ((Plugin.ContractCompletedAt <= -99999f) ? 0f : (now - Plugin.ContractCompletedAt)); if (flag && num3 >= 5.35f) { SetInactive("contract-complete-faded"); return; } bool flag2 = accumulatedLockedBountyTotal > 0; List list4 = new List(); list4.Add(flag2 ? $"{FormatCompactMoney(accumulatedLockedBountyTotal)} / {FormatCompactMoney(missionTotalBountyCeiling)} EARNED" : $"BOUNTY HUNTERS"); List list5 = list4; foreach (KeyValuePair item in Plugin.LockedBountyByEnemyName) { int value; int num4 = (Plugin.LastRenderedBountyByEnemyName.TryGetValue(item.Key, out value) ? value : 0); if (item.Value > num4) { Plugin.MoneyFlashUntilByEnemyName[item.Key] = now + 0.26f; Plugin.MoneyBounceUntilByEnemyName[item.Key] = now + 0.24f; } Plugin.LastRenderedBountyByEnemyName[item.Key] = item.Value; } HashSet hashSet = new HashSet(list.Select((EnemyViewState x) => x.Name), StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(list2, StringComparer.OrdinalIgnoreCase); HashSet hashSet3 = new HashSet(list3.Select((KeyValuePair x) => x.Key), StringComparer.OrdinalIgnoreCase); foreach (string item2 in list.Select((EnemyViewState x) => x.Name).Concat(list2).Concat(list3.Select((KeyValuePair x) => x.Key)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(GetStableOrder) .ThenBy((string x) => x)) { bool flag3 = hashSet.Contains(item2); bool flag4 = hashSet2.Contains(item2); bool flag5 = hashSet3.Contains(item2); if (flag3 || flag4 || flag5) { list5.Add(FormatMobLine(item2, flag3)); } } if (list5.Count <= 1) { if (!((Object)(object)Plugin.Root != (Object)null) || !Plugin.Root.activeSelf || !(now < Plugin.HudContentHoldUntil) || string.IsNullOrEmpty(Plugin.LastHudRenderedText)) { SetInactive($"no-renderable-lines | alive={list.Count} | locked={list3.Count} | sticky={list2.Count}"); } return; } bool activeSelf = Plugin.Root.activeSelf; Plugin.Root.SetActive(true); if (!activeSelf) { Plugin.HudShowStartedAt = now; } 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, now - 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", list5.Select((string line) => (!line.Contains("{line}" : line)); ((TMP_Text)Plugin.Text).text = text3; Plugin.LastHudRenderedText = text3; Plugin.HudContentHoldUntil = now + 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); 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}{enemyName} +${FormatCompactMoney(value2)}"; } if (isAlive && value == 0) { int rewardPreviewForEnemy = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy > 0) { return text4 + "" + enemyName + " $" + FormatCompactMoney(rewardPreviewForEnemy) + ""; } } if (isAlive && value > 0) { int rewardPreviewForEnemy2 = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy2 > 0) { return text4 + "" + enemyName + " +$" + FormatCompactMoney(value2) + " next $" + FormatCompactMoney(rewardPreviewForEnemy2) + ""; } } string text6 = (isAlive ? text2 : text3); return text4 + "" + enemyName + ""; } 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, ["Banger"] = 0, ["Cleanup Crew"] = 5000, ["Clown"] = 5000, ["Headman"] = 5000, ["Huntsman"] = 5000, ["Loom"] = 5000, ["Reaper"] = 5000, ["Robe"] = 5000, ["Trudge"] = 5000 }; internal static bool TryGetReward(string enemyName, out int reward) { if (string.IsNullOrWhiteSpace(enemyName)) { reward = 0; return false; } return RewardsByEnemyName.TryGetValue(enemyName.Trim(), 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 { 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() { 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 text = (string.IsNullOrWhiteSpace(item.enemyName) ? "Monster" : item.enemyName); bool flag = IsEligibleForBounty(text); TrackEncounteredEnemyType(text, flag); 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) || num2 <= 0); 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 (Plugin.FinalPhaseEntered && flag && value.Alive && flag2 && !value.RewardGrantedThisLife) { RegisterKillEvent(text); value.RewardGrantedThisLife = true; } if (flag3) { value.RewardGrantedThisLife = false; } value.Name = text; value.Alive = flag3; value.Spawned = parentSpawned; value.Dead = flag2; value.CurrentHp = num2; value.EligibleForBounty = flag; value.BountyLocked = Plugin.LockedBountyByEnemyName.TryGetValue(text, out var value2) && value2 > 0; } else { Plugin.EnemyViews[instanceID] = new EnemyViewState { Name = text, Alive = flag3, Spawned = parentSpawned, Dead = flag2, CurrentHp = num2, EligibleForBounty = flag, BountyLocked = (Plugin.LockedBountyByEnemyName.TryGetValue(text, out var value3) && value3 > 0), RewardGrantedThisLife = false }; } } foreach (int item2 in Plugin.EnemyViews.Keys.Where((int id) => !seen.Contains(id)).ToList()) { Plugin.EnemyViews.Remove(item2); } } 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)) { int num; string text; if (Plugin.ClaimedUniqueEnemyTypes.Add(enemyName)) { num = reward; text = "unique"; } else { int remainingRepeatBountyCap = BountyMoney.GetRemainingRepeatBountyCap(); if (remainingRepeatBountyCap <= 0) { Plugin.DebugLog($"[BountyDebug] Reward skipped | mob={enemyName} | reason=repeat-cap-exhausted | repeatCap={Plugin.GetRepeatBountyCap():N0}"); return; } num = Plugin.FloorToBountyStep(Mathf.Min(reward, remainingRepeatBountyCap)); text = "repeat"; } if (num <= 0) { Plugin.DebugLog($"[BountyDebug] Reward skipped | mob={enemyName} | reason=awarded-zero | reward={reward:N0} | repeatRemaining={BountyMoney.GetRemainingRepeatBountyCap():N0}"); return; } LockBountyReward(enemyName, num, unscaledTime, text == "unique"); Plugin.DebugLog($"[BountyDebug] Reward locked | mob={enemyName} | source={text} | reward={reward:N0} | awarded={num:N0} | enemyTotal={Plugin.LockedBountyByEnemyName[enemyName]:N0} | totalEarned={BountyMoney.GetAccumulatedLockedBountyTotal():N0} | uniqueEarned={Plugin.UniqueBountyEarnedTotal:N0} | repeatEarned={Plugin.RepeatBountyEarnedTotal:N0} | repeatCap={Plugin.GetRepeatBountyCap():N0} | missionCeiling={Plugin.GetMissionTotalBountyCeiling():N0}"); } else if (Plugin.LoggedSimpleRewardMissingNames.Add(enemyName)) { Plugin.Logger.LogWarning((object)("[BountyHunters] Simple bounty rewards has no reward entry for enemyName='" + enemyName + "'. Add alias/mapping before trusting this enemy payout.")); } } internal static void LockBountyReward(string enemyName, int reward, float now, bool uniqueClaim) { string enemyName2 = enemyName; int num = Mathf.Max(0, reward); 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; } else { Plugin.RepeatBountyEarnedTotal += num; } if (Plugin.KillCountByEnemyName.ContainsKey(enemyName2)) { Plugin.KillCountByEnemyName[enemyName2]++; } else { Plugin.KillCountByEnemyName[enemyName2] = 1; } Plugin.LastLockedBountyAt = now; Plugin.ShopMoneyOverrideArmed = true; Plugin.PersistedShopLockedBountyTotal = BountyMoney.GetAccumulatedLockedBountyTotal(); } 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; } } } [BepInPlugin("denis.repo.bountyhuntersui", "Bounty Hunters", "1.2.0")] 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 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); internal static readonly FieldInfo? EnemyHealthDeadField = typeof(EnemyHealth).GetField("dead", 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 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; private 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; public static bool HasOverlayExport => BountyBridge.HasOverlayExport; 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", 25, new ConfigDescription("Limits repeat-kill bonus payouts to this percent of haul earned during the current level.", (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(); 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; LevelStartHaulSnapshot = -1; InjectedLockedBountyTotal = 0; LastLockedBountyAt = -100000f; HudShowStartedAt = -100000f; HudContentHoldUntil = -100000f; ContractCompletedAt = -100000f; LastHudRenderedText = string.Empty; LoggedSpawnTableEnemyNamesThisLevel = 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 GetBaseExtractedHaul() { int num = Mathf.Max(0, LevelStartHaulSnapshot); int currentHaulWithoutInjectedBounty = GetCurrentHaulWithoutInjectedBounty(); return Mathf.Max(0, currentHaulWithoutInjectedBounty - num); } internal static int GetRepeatBountyCap() { float num = (float)Mathf.Clamp(BountyCapPercent?.Value ?? 25, 25, 100) / 100f; return FloorToBountyStep(Mathf.Max(0, Mathf.RoundToInt((float)GetBaseExtractedHaul() * num))); } 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 int GetMissionTotalBountyCeiling() { return GetEncounteredUniqueBountyPool() + GetRepeatBountyCap(); } internal static int FloorToBountyStep(int amount) { if (amount <= 0) { return 0; } return amount / 1000 * 1000; } 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; } public static int GetOverlayLockedBountyTotal() { return BountyBridge.GetOverlayLockedBountyTotal(); } public static bool ShouldOverlayShowBounty() { return BountyBridge.ShouldOverlayShowBounty(); } public static int GetFinalShopTargetMoney() { return BountyBridge.GetFinalShopTargetMoney(); } internal static int GetAccumulatedLockedBountyTotal() { return BountyMoney.GetAccumulatedLockedBountyTotal(); } internal static void SyncRunCurrencyToPersistedTarget(string source) { BountyMoney.SyncRunCurrencyToPersistedTarget(source); } internal static void InjectLockedBountyIntoFinalHaul(string source) { BountyMoney.InjectLockedBountyIntoFinalHaul(source); } internal static int GetShopAdjustedTotalHaul(int baseValue) { return BountyMoney.GetShopAdjustedTotalHaul(baseValue); } internal static int GetShopAdjustedCurrencyK(int baseValue) { return BountyMoney.GetShopAdjustedCurrencyK(baseValue); } } }