Decompiled source of Bounty Hunters v1.2.0

plugins/BountyHunters/BountyHunters.dll

Decompiled 6 minutes ago
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<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);
		}
	}
}