Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Bounty Hunters v1.2.0
plugins/BountyHunters/BountyHunters.dll
Decompiled 6 minutes agousing 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<object>())); Plugin.StatSetRunCurrencyMethod.Invoke(obj2, new object[1] { num }); int num3 = Convert.ToInt32(Plugin.StatGetRunCurrencyMethod.Invoke(obj, Array.Empty<object>())); 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<TMP_Text>() : 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<TextMeshProUGUI>(); ((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<RectTransform>(); 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<EnemyViewState> list = (from g in Plugin.EnemyViews.Values.Where((EnemyViewState x) => x.Alive && x.EligibleForBounty).GroupBy<EnemyViewState, string>((EnemyViewState x) => x.Name, StringComparer.OrdinalIgnoreCase) select g.First() into x orderby GetStableOrder(x.Name), x.Name select x).ToList(); List<string> list2 = (from x in Plugin.StickyDisplayUntilByEnemyName where x.Value > now select x.Key into x orderby GetStableOrder(x), x select x).ToList(); List<KeyValuePair<string, int>> 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<string> list4 = new List<string>(); list4.Add(flag2 ? $"<color={text2}><size={num2}><b>{FormatCompactMoney(accumulatedLockedBountyTotal)} / {FormatCompactMoney(missionTotalBountyCeiling)} EARNED</b></size></color>" : $"<color={text}><size={num2}><b>BOUNTY HUNTERS</b></size></color>"); List<string> list5 = list4; foreach (KeyValuePair<string, int> 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<string> hashSet = new HashSet<string>(list.Select((EnemyViewState x) => x.Name), StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet2 = new HashSet<string>(list2, StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet3 = new HashSet<string>(list3.Select((KeyValuePair<string, int> x) => x.Key), StringComparer.OrdinalIgnoreCase); foreach (string item2 in list.Select((EnemyViewState x) => x.Name).Concat(list2).Concat(list3.Select((KeyValuePair<string, int> x) => x.Key)) .Distinct<string>(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<RectTransform>().anchoredPosition = new Vector2(10f + num8, -120f); string text3 = string.Join("\n", list5.Select((string line) => (!line.Contains("<size=")) ? $"<size={rowSize}>{line}</size>" : 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) ? $"<color={text}>x{value}</color> " : 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}<color={text2}>{enemyName}</color> <b><size={num3}><color={text5}>+${FormatCompactMoney(value2)}</color></size></b>"; } if (isAlive && value == 0) { int rewardPreviewForEnemy = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy > 0) { return text4 + "<color=" + text2 + ">" + enemyName + "</color> <color=" + text + ">$" + FormatCompactMoney(rewardPreviewForEnemy) + "</color>"; } } if (isAlive && value > 0) { int rewardPreviewForEnemy2 = BountyMoney.GetRewardPreviewForEnemy(enemyName); if (rewardPreviewForEnemy2 > 0) { return text4 + "<color=" + text2 + ">" + enemyName + "</color> <b><color=#06FD06>+$" + FormatCompactMoney(value2) + "</color></b> <color=" + text + ">next $" + FormatCompactMoney(rewardPreviewForEnemy2) + "</color>"; } } string text6 = (isAlive ? text2 : text3); return text4 + "<color=" + text6 + ">" + enemyName + "</color>"; } 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<string, int> RewardsByEnemyName = new Dictionary<string, int>(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<int> seen = new HashSet<int>(); 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<string>(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<string>(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<bool> Enabled = null; internal static ConfigEntry<bool> ShowBountyText = null; internal static ConfigEntry<int> BountyCapPercent = null; internal static ConfigEntry<float> UiScale = null; internal static ConfigEntry<TextColorMode> TextColor = null; internal static readonly Dictionary<int, EnemyViewState> EnemyViews = new Dictionary<int, EnemyViewState>(); internal static readonly Dictionary<string, int> LockedBountyByEnemyName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, int> KillCountByEnemyName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet<string> EncounteredRewardableEnemyTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet<string> ClaimedUniqueEnemyTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, int> LastRenderedBountyByEnemyName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, float> MoneyFlashUntilByEnemyName = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, float> MoneyBounceUntilByEnemyName = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet<string> LoggedSimpleRewardMissingNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet<string> LoggedHudPreviewRewardNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet<string> LoggedHudPreviewMissingNames = new HashSet<string>(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<string, float> StickyDisplayUntilByEnemyName = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary<string, int> StableOrderByEnemyName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); internal static readonly List<string> EnemySlots = new List<string>(); internal static readonly Dictionary<int, EnemyHealth> TrackedEnemyHealths = new Dictionary<int, EnemyHealth>(); internal static readonly Dictionary<int, int> OriginalSpawnValuableMaxByEnemyHealthId = new Dictionary<int, int>(); 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<string> NoBountyEnemyNames = new HashSet<string>(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<bool>("General", "Bounty Hunters", true, "Globally enables or disables the Bounty Hunters mod."); ShowBountyText = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Snow Text", true, "Shows or hides the Bounty Hunters UI text without disabling bounty logic."); BountyCapPercent = ((BaseUnityPlugin)this).Config.Bind<int>("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<int>(25, 100), Array.Empty<object>())); TextColor = ((BaseUnityPlugin)this).Config.Bind<TextColorMode>("General", "Text Color", TextColorMode.Orange, "Controls the Bounty Hunters header/body accent style."); UiScale = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Text Scale", 1f, new ConfigDescription("Scales Bounty Hunters text. 1.0 = maximum size baseline.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.7f, 1f), Array.Empty<object>())); 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, object>)(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<int> list = new List<int>(); foreach (KeyValuePair<int, EnemyHealth> 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<int, EnemyHealth> 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<bool>(); } 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<object>()); 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); } } }