Decompiled source of EvilTwinsMods v0.5.1

plugins\EvilTwinsMods.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("EvilTwinsMods")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("EvilTwinsMods")]
[assembly: AssemblyTitle("EvilTwinsMods")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace EvilTwinsMods
{
	internal static class PluginInfo
	{
		public const string GUID = "eviltwins.repo.mods";

		public const string NAME = "EvilTwins Mods";

		public const string VERSION = "0.5.1";
	}
	internal readonly struct ConfigSettings
	{
		public bool Enabled { get; }

		public int MinBoxes { get; }

		public int MaxBoxes { get; }

		public bool RarityWeightsEnabled { get; }

		public int CommonWeight { get; }

		public int UncommonWeight { get; }

		public int RareWeight { get; }

		public int UltraRareWeight { get; }

		public int TotalRarityWeight => CommonWeight + UncommonWeight + RareWeight + UltraRareWeight;

		public ConfigSettings(bool enabled, int minBoxes, int maxBoxes, bool rarityWeightsEnabled, int commonWeight, int uncommonWeight, int rareWeight, int ultraRareWeight)
		{
			minBoxes = Mathf.Clamp(minBoxes, 0, 20);
			maxBoxes = Mathf.Clamp(maxBoxes, 0, 20);
			if (maxBoxes < minBoxes)
			{
				int num = minBoxes;
				minBoxes = maxBoxes;
				maxBoxes = num;
			}
			Enabled = enabled;
			MinBoxes = minBoxes;
			MaxBoxes = maxBoxes;
			RarityWeightsEnabled = rarityWeightsEnabled;
			CommonWeight = Mathf.Clamp(commonWeight, 0, 100);
			UncommonWeight = Mathf.Clamp(uncommonWeight, 0, 100);
			RareWeight = Mathf.Clamp(rareWeight, 0, 100);
			UltraRareWeight = Mathf.Clamp(ultraRareWeight, 0, 100);
		}

		public int GetRarityWeight(int rarityIndex)
		{
			return rarityIndex switch
			{
				0 => CommonWeight, 
				1 => UncommonWeight, 
				2 => RareWeight, 
				3 => UltraRareWeight, 
				_ => 0, 
			};
		}
	}
	internal readonly struct LevelSettings
	{
		public bool Enabled { get; }

		public int MinBoxes { get; }

		public int MaxBoxes { get; }

		public int TargetBoxes { get; }

		public bool RarityWeightsEnabled { get; }

		public int CommonWeight { get; }

		public int UncommonWeight { get; }

		public int RareWeight { get; }

		public int UltraRareWeight { get; }

		public int LoopMax { get; }

		public int CompensationLoopMax { get; }

		public int TotalRarityWeight => CommonWeight + UncommonWeight + RareWeight + UltraRareWeight;

		public LevelSettings(ConfigSettings config, int targetBoxes)
		{
			Enabled = config.Enabled;
			MinBoxes = config.MinBoxes;
			MaxBoxes = config.MaxBoxes;
			TargetBoxes = Mathf.Clamp(targetBoxes, 0, 20);
			RarityWeightsEnabled = config.RarityWeightsEnabled;
			CommonWeight = config.CommonWeight;
			UncommonWeight = config.UncommonWeight;
			RareWeight = config.RareWeight;
			UltraRareWeight = config.UltraRareWeight;
			LoopMax = GetTimeFormulaLoopMax(TargetBoxes);
			CompensationLoopMax = GetCompensationLoopMax(TargetBoxes);
		}

		public int GetRarityWeight(int rarityIndex)
		{
			return rarityIndex switch
			{
				0 => CommonWeight, 
				1 => UncommonWeight, 
				2 => RareWeight, 
				3 => UltraRareWeight, 
				_ => 0, 
			};
		}

		private static int GetTimeFormulaLoopMax(int boxCount)
		{
			if (boxCount <= 0)
			{
				return -1;
			}
			return boxCount - 1;
		}

		private static int GetCompensationLoopMax(int boxCount)
		{
			if (boxCount <= 0)
			{
				return -1;
			}
			return boxCount * 3 - 1;
		}
	}
	[BepInPlugin("eviltwins.repo.mods", "EvilTwins Mods", "0.5.1")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static string PluginDirectory;

		private static ConfigEntry<bool> _hostAuthoritativeConfig;

		private static ConfigEntry<bool> _logHostAuthority;

		private static ConfigEntry<bool> _cosmeticBoxRangeEnabled;

		private static ConfigEntry<int> _minBoxes;

		private static ConfigEntry<int> _maxBoxes;

		private static ConfigEntry<bool> _rarityWeightsEnabled;

		private static ConfigEntry<int> _commonWeight;

		private static ConfigEntry<int> _uncommonWeight;

		private static ConfigEntry<int> _rareWeight;

		private static ConfigEntry<int> _ultraRareWeight;

		private static ConfigEntry<KeyboardShortcut> _reloadConfigShortcut;

		private static ConfigEntry<bool> _logRarityDiagnostics;

		private static ConfigEntry<bool> _shopTweaksEnabled;

		private static ConfigEntry<bool> _shopTweaksLogDetails;

		private static ConfigEntry<int> _upgradePricePercent;

		private static ConfigEntry<int> _weaponPricePercent;

		private static ConfigEntry<int> _dronePricePercent;

		private static ConfigEntry<int> _healthPackPricePercent;

		private static ConfigEntry<int> _crystalPricePercent;

		private static ConfigEntry<bool> _disableMultiplayerPriceScaling;

		private static ConfigEntry<bool> _disableOwnedUpgradePriceScaling;

		private static ConfigEntry<bool> _disableLevelProgressPriceScaling;

		private static ConfigEntry<int> _multiplayerScalingPerExtraPlayerPercent;

		private static ConfigEntry<int> _ownedUpgradeScalingPercent;

		private static ConfigEntry<int> _levelProgressScalingPercent;

		private static ConfigEntry<bool> _upgradeSharingEnabled;

		private static ConfigEntry<bool> _upgradeSharingLogDetails;

		private static ConfigEntry<bool> _upgradeSharingMatchHighestTeamLevel;

		private static ConfigEntry<bool> _upgradeSharingHealHealthUpgrades;

		private static readonly object SettingsLock = new object();

		private static ConfigSettings _pendingConfig = new ConfigSettings(enabled: true, 1, 4, rarityWeightsEnabled: false, 60, 25, 12, 3);

		private static LevelSettings _activeSettings = new LevelSettings(new ConfigSettings(enabled: true, 1, 4, rarityWeightsEnabled: false, 60, 25, 12, 3), 1);

		private static int _actualBoxesThisLevel;

		internal static LevelSettings ActiveSettings
		{
			get
			{
				lock (SettingsLock)
				{
					return _activeSettings;
				}
			}
		}

		internal static bool RarityDiagnosticsEnabled
		{
			get
			{
				if (_logRarityDiagnostics != null)
				{
					return _logRarityDiagnostics.Value;
				}
				return false;
			}
		}

		internal static bool HostAuthoritativeConfigEnabled
		{
			get
			{
				if (_hostAuthoritativeConfig != null)
				{
					return _hostAuthoritativeConfig.Value;
				}
				return true;
			}
		}

		private void Awake()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Expected O, but got Unknown
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Expected O, but got Unknown
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Expected O, but got Unknown
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Expected O, but got Unknown
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Expected O, but got Unknown
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Expected O, but got Unknown
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bf: Expected O, but got Unknown
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Expected O, but got Unknown
			//IL_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_032b: Expected O, but got Unknown
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: Expected O, but got Unknown
			//IL_038d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0397: Expected O, but got Unknown
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cd: Expected O, but got Unknown
			//IL_03ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f8: Expected O, but got Unknown
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_0423: Expected O, but got Unknown
			//IL_0444: Unknown result type (might be due to invalid IL or missing references)
			//IL_044e: Expected O, but got Unknown
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0485: Expected O, but got Unknown
			//IL_04b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bb: Expected O, but got Unknown
			//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f1: Expected O, but got Unknown
			//IL_0512: Unknown result type (might be due to invalid IL or missing references)
			//IL_051c: Expected O, but got Unknown
			//IL_053d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0547: Expected O, but got Unknown
			//IL_0568: Unknown result type (might be due to invalid IL or missing references)
			//IL_0572: Expected O, but got Unknown
			//IL_0593: Unknown result type (might be due to invalid IL or missing references)
			//IL_059d: Expected O, but got Unknown
			//IL_0699: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty;
			CleanupObsoleteConfigSections();
			_hostAuthoritativeConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HostAuthoritativeConfig", true, new ConfigDescription("If true, gameplay-changing EvilTwins Mods settings are controlled by the host/singleplayer only. Non-host client configs are ignored for host-controlled features like cosmetic boxes, shop prices, and upgrade sharing.", (AcceptableValueBase)null, Array.Empty<object>()));
			_logHostAuthority = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LogHostAuthority", true, new ConfigDescription("Print whether this client is allowed to apply host-controlled EvilTwins Mods gameplay settings.", (AcceptableValueBase)null, Array.Empty<object>()));
			_cosmeticBoxRangeEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Cosmetic Box Range", "Enabled", true, new ConfigDescription("Enable the cosmetic box range module. If false, EvilTwins Mods leaves cosmetic box spawning vanilla.", (AcceptableValueBase)null, Array.Empty<object>()));
			_minBoxes = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Range", "MinBoxes", 1, new ConfigDescription("Minimum cosmetic boxes to target per level. Clamped 0-20. Changes apply on the next level load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
			_maxBoxes = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Range", "MaxBoxes", 4, new ConfigDescription("Maximum cosmetic boxes to target per level. Clamped 0-20. Changes apply on the next level load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
			_rarityWeightsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Cosmetic Box Rarity Weights", "Enabled", false, new ConfigDescription("Enable weighted cosmetic box rarity selection. If false, EvilTwins Mods leaves cosmetic box rarity vanilla but still logs the rarity that spawned.", (AcceptableValueBase)null, Array.Empty<object>()));
			_commonWeight = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Rarity Weights", "CommonWeight", 60, new ConfigDescription("Relative weight for rarity index 0, usually Common. Range 0-100. Set to 0 to prevent this rarity when weighted rarity is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			_uncommonWeight = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Rarity Weights", "UncommonWeight", 25, new ConfigDescription("Relative weight for rarity index 1, usually Uncommon. Range 0-100. Set to 0 to prevent this rarity when weighted rarity is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			_rareWeight = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Rarity Weights", "RareWeight", 12, new ConfigDescription("Relative weight for rarity index 2, usually Rare/Epic. Range 0-100. Set to 0 to prevent this rarity when weighted rarity is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			_ultraRareWeight = ((BaseUnityPlugin)this).Config.Bind<int>("Cosmetic Box Rarity Weights", "UltraRareWeight", 3, new ConfigDescription("Relative weight for rarity index 3, usually Ultra Rare/Legendary. Range 0-100. Set to 0 to prevent this rarity when weighted rarity is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			_reloadConfigShortcut = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ReloadConfigShortcut", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), new ConfigDescription("Reload EvilTwins Mods config while in-game/lobby. Rarity-weight changes apply immediately for later spawns. Min/Max box range is picked fresh on the next level load.", (AcceptableValueBase)null, Array.Empty<object>()));
			_logRarityDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogRarityDiagnostics", false, new ConfigDescription("Logs extra rarity mapping/debug info to help verify which internal rarity index matches the visible cosmetic box type.", (AcceptableValueBase)null, Array.Empty<object>()));
			_shopTweaksEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Shop Tweaks", "Enabled", false, new ConfigDescription("Enable EvilTwins shop price changes. Host/singleplayer should control this.", (AcceptableValueBase)null, Array.Empty<object>()));
			_shopTweaksLogDetails = ((BaseUnityPlugin)this).Config.Bind<bool>("Shop Tweaks", "LogDetails", true, new ConfigDescription("Print shop price details to the console/log. Useful while tuning price settings.", (AcceptableValueBase)null, Array.Empty<object>()));
			_upgradePricePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Prices", "UpgradePricePercent", 100, new ConfigDescription("Base price percent for upgrade items that are not detected as weapons or drones. 100 = vanilla-ish, 50 = half price, 200 = double price.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>()));
			_weaponPricePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Prices", "WeaponPricePercent", 100, new ConfigDescription("Base price percent for items detected as weapons. 50 = half price.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>()));
			_dronePricePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Prices", "DronePricePercent", 100, new ConfigDescription("Base price percent for items detected as drones. 30 = 30% normal cost.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>()));
			_healthPackPricePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Prices", "HealthPackPricePercent", 100, new ConfigDescription("Base price percent for health packs. 100 = normal, 50 = half price.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>()));
			_crystalPricePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Prices", "CrystalPricePercent", 100, new ConfigDescription("Base price percent for energy crystals. 100 = normal, 50 = half price.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>()));
			_disableMultiplayerPriceScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("Shop Scaling", "DisableMultiplayerPriceScaling", false, new ConfigDescription("If true, shop price calculations ignore extra-player multiplayer scaling.", (AcceptableValueBase)null, Array.Empty<object>()));
			_disableOwnedUpgradePriceScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("Shop Scaling", "DisableOwnedUpgradePriceScaling", false, new ConfigDescription("If true, upgrade price calculations ignore the increasing price from already-owned/purchased copies of that upgrade.", (AcceptableValueBase)null, Array.Empty<object>()));
			_disableLevelProgressPriceScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("Shop Scaling", "DisableLevelProgressPriceScaling", false, new ConfigDescription("If true, health pack and crystal price calculations ignore level/run progress scaling.", (AcceptableValueBase)null, Array.Empty<object>()));
			_multiplayerScalingPerExtraPlayerPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Scaling", "MultiplayerScalingPerExtraPlayerPercent", 10, new ConfigDescription("Extra price percent per additional player when multiplayer scaling is enabled. 10 = +10% per extra player, 0 = no scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-90, 500), Array.Empty<object>()));
			_ownedUpgradeScalingPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Scaling", "OwnedUpgradeScalingPercent", 100, new ConfigDescription("Multiplier applied to the game's owned-upgrade price increase. 100 = vanilla amount, 0 = off, 50 = half as much.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 500), Array.Empty<object>()));
			_levelProgressScalingPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Shop Scaling", "LevelProgressScalingPercent", 100, new ConfigDescription("Multiplier applied to the game's health pack/crystal level-progress price increase. 100 = vanilla amount, 0 = off, 50 = half as much.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 500), Array.Empty<object>()));
			_upgradeSharingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade Sharing", "Enabled", false, new ConfigDescription("Enable team-wide upgrade sharing. Host/singleplayer controls this. When one player uses an upgrade, the rest of the lobby receives matching upgrade levels.", (AcceptableValueBase)null, Array.Empty<object>()));
			_upgradeSharingLogDetails = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade Sharing", "LogDetails", true, new ConfigDescription("Print upgrade-sharing detection and distribution details to the console/log.", (AcceptableValueBase)null, Array.Empty<object>()));
			_upgradeSharingMatchHighestTeamLevel = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade Sharing", "MatchHighestTeamLevel", true, new ConfigDescription("If true, lower-level players are brought up to the highest team level for the upgraded stat. If false, other players only receive the same +amount that the buyer gained.", (AcceptableValueBase)null, Array.Empty<object>()));
			_upgradeSharingHealHealthUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade Sharing", "HealSharedHealthUpgrades", false, new ConfigDescription("If true, players receiving a shared health upgrade are also healed by roughly the added max-health amount. Leave false if you only want max-health sharing.", (AcceptableValueBase)null, Array.Empty<object>()));
			UpgradeSharing.Configure(_upgradeSharingEnabled.Value, _upgradeSharingLogDetails.Value, _upgradeSharingMatchHighestTeamLevel.Value, _upgradeSharingHealHealthUpgrades.Value);
			ShopTweaks.Configure(_shopTweaksEnabled.Value, _shopTweaksLogDetails.Value, _upgradePricePercent.Value, _weaponPricePercent.Value, _dronePricePercent.Value, _healthPackPricePercent.Value, _crystalPricePercent.Value, _disableMultiplayerPriceScaling.Value, _disableOwnedUpgradePriceScaling.Value, _disableLevelProgressPriceScaling.Value, _multiplayerScalingPerExtraPlayerPercent.Value, _ownedUpgradeScalingPercent.Value, _levelProgressScalingPercent.Value);
			UpdatePendingSettings();
			ActivatePendingSettingsForStartup();
			LogHostAuthorityStatus("startup");
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				UpdatePendingSettings();
				UpdateShopTweaksFromConfig();
				UpdateUpgradeSharingFromConfig();
				ApplyPendingSettingsToCurrentLevelKeepTarget("config setting changed");
				LogHostAuthorityStatus("config setting changed");
			};
			bool flag = TryPatchAll(new Harmony("eviltwins.repo.mods"));
			Log.LogInfo((object)("EvilTwins Mods v0.5.1 loaded - patches " + (flag ? "enabled" : "disabled") + "; chance gate " + (PatchMethods.ChanceGatePatchActive ? "active" : "inactive") + "; extraction limit " + (PatchMethods.ExtractionLimitPatchActive ? "active" : "inactive") + "; active " + Describe(ActiveSettings)));
		}

		private void CleanupObsoleteConfigSections()
		{
			try
			{
				ConfigFile config = ((BaseUnityPlugin)this).Config;
				string text = ((config != null) ? config.ConfigFilePath : null);
				if (string.IsNullOrWhiteSpace(text) || !File.Exists(text))
				{
					return;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Shop Filters", "Shop Upgrade Filters", "Valuable Tracking", "Truck Value Screen", "Truck Value Screen Position", "Truck Value Screen Debug" };
				string[] array = File.ReadAllLines(text);
				List<string> list = new List<string>(array.Length);
				bool flag = false;
				int num = 0;
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					string text3 = text2.Trim();
					if (text3.StartsWith("[") && text3.EndsWith("]") && text3.Length > 2)
					{
						string item = text3.Substring(1, text3.Length - 2).Trim();
						flag = hashSet.Contains(item);
						if (flag)
						{
							num++;
							continue;
						}
					}
					if (!flag)
					{
						list.Add(text2);
					}
				}
				if (num <= 0)
				{
					return;
				}
				List<string> list2 = new List<string>(list.Count);
				int num2 = 0;
				foreach (string item2 in list)
				{
					if (string.IsNullOrWhiteSpace(item2))
					{
						num2++;
						if (num2 > 2)
						{
							continue;
						}
					}
					else
					{
						num2 = 0;
					}
					list2.Add(item2);
				}
				File.WriteAllLines(text, list2.ToArray());
				((BaseUnityPlugin)this).Config.Reload();
				ConsoleInfo($"Cleaned {num} obsolete config section(s).");
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)$"Config cleanup skipped after an error. {arg}");
				}
			}
		}

		private static bool TryRawInputGetKeyDown(KeyCode key)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Type type = AccessTools.TypeByName("UnityEngine.Input");
				if (type == null)
				{
					type = Type.GetType("UnityEngine.Input, UnityEngine.InputLegacyModule");
				}
				if (type == null)
				{
					return false;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "GetKeyDown", new Type[1] { typeof(KeyCode) }, (Type[])null);
				if (methodInfo == null)
				{
					return false;
				}
				object obj = methodInfo.Invoke(null, new object[1] { key });
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			}
			catch
			{
				return false;
			}
		}

		private void Update()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_reloadConfigShortcut != null)
				{
					KeyboardShortcut value = _reloadConfigShortcut.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						((BaseUnityPlugin)this).Config.Reload();
						UpdatePendingSettings();
						UpdateShopTweaksFromConfig();
						UpdateUpgradeSharingFromConfig();
						ApplyPendingSettingsToCurrentLevelKeepTarget("manual config reload");
						LogHostAuthorityStatus("manual config reload");
						ConsoleInfo("Config reloaded from file. Box target range changes will be used on the next level load; rarity-weight changes apply to remaining spawn attempts.");
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)$"Failed to reload EvilTwins Mods config. {arg}");
				}
			}
		}

		private static bool TryPatchAll(Harmony harmony)
		{
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Expected O, but got Unknown
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Expected O, but got Unknown
			//IL_0255: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Expected O, but got Unknown
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_0290: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(ValuableDirector), "SetupHost", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsGet", (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsClampedGet", (Type[])null, (Type[])null);
				MethodInfo methodInfo4 = AccessTools.Method(typeof(ValuableDirector), "SpawnCosmeticWorldObject", (Type[])null, (Type[])null);
				MethodInfo methodInfo5 = AccessTools.Method(typeof(CosmeticWorldObject), "ExtractRPC", (Type[])null, (Type[])null);
				MethodInfo iteratorMoveNext = GetIteratorMoveNext(methodInfo);
				FieldInfo fieldInfo = AccessTools.Field(typeof(ValuableDirector), "cosmeticWorldObjectTargetAmount");
				if (methodInfo == null || iteratorMoveNext == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || methodInfo5 == null || fieldInfo == null)
				{
					Log.LogError((object)("Required game methods were not found; EvilTwins Mods will not patch anything. SetupHost=" + ((methodInfo != null) ? "ok" : "missing") + ", SetupHost.MoveNext=" + ((iteratorMoveNext != null) ? "ok" : "missing") + ", CosmeticWorldObjectLevelLoopsGet=" + ((methodInfo2 != null) ? "ok" : "missing") + ", CosmeticWorldObjectLevelLoopsClampedGet=" + ((methodInfo3 != null) ? "ok" : "missing") + ", SpawnCosmeticWorldObject=" + ((methodInfo4 != null) ? "ok" : "missing") + ", ExtractRPC=" + ((methodInfo5 != null) ? "ok" : "missing") + ", cosmeticWorldObjectTargetAmount=" + ((fieldInfo != null) ? "ok" : "missing")));
					return false;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PatchMethods), "SetupHostPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "LoopsGetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "LoopsClampedGetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)iteratorMoveNext, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "SetupHostMoveNextTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				LogSpawnMethodSignature(methodInfo4);
				PatchSpawnCosmeticWorldObject(harmony, methodInfo4);
				harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "ExtractRpcTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				TryPatchShopTweaks(harmony);
				TryPatchUpgradeSharing(harmony);
				return true;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Failed to register EvilTwins Mods patches; attempting rollback. {arg}");
				try
				{
					harmony.UnpatchSelf();
					PatchMethods.ResetPatchState();
					Log.LogInfo((object)"Rolled back EvilTwins Mods patches after registration failure.");
				}
				catch (Exception arg2)
				{
					PatchMethods.ResetPatchState();
					Log.LogError((object)$"Failed to roll back partial EvilTwins Mods patches. {arg2}");
				}
				return false;
			}
		}

		private static void UpdateShopTweaksFromConfig()
		{
			if (_shopTweaksEnabled != null)
			{
				ShopTweaks.Configure(_shopTweaksEnabled.Value, _shopTweaksLogDetails.Value, _upgradePricePercent.Value, _weaponPricePercent.Value, _dronePricePercent.Value, _healthPackPricePercent.Value, _crystalPricePercent.Value, _disableMultiplayerPriceScaling.Value, _disableOwnedUpgradePriceScaling.Value, _disableLevelProgressPriceScaling.Value, _multiplayerScalingPerExtraPlayerPercent.Value, _ownedUpgradeScalingPercent.Value, _levelProgressScalingPercent.Value);
			}
		}

		private static void TryPatchShopTweaks(Harmony harmony)
		{
			try
			{
				ShopTweaks.TryPatch(harmony);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Shop Tweaks patches failed. Cosmetic box patches remain active. {arg}");
			}
		}

		private static void TryPatchUpgradeSharing(Harmony harmony)
		{
			try
			{
				UpgradeSharing.TryPatch(harmony);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Upgrade Sharing patches failed. Cosmetic box/shop patches remain active. {arg}");
			}
		}

		private static void UpdateUpgradeSharingFromConfig()
		{
			if (_upgradeSharingEnabled != null)
			{
				UpgradeSharing.Configure(_upgradeSharingEnabled.Value, _upgradeSharingLogDetails.Value, _upgradeSharingMatchHighestTeamLevel.Value, _upgradeSharingHealHealthUpgrades.Value);
			}
		}

		private static void PatchSpawnCosmeticWorldObject(Harmony harmony, MethodInfo spawnCosmeticWorldObject)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			//IL_009a: Expected O, but got Unknown
			try
			{
				harmony.Patch((MethodBase)spawnCosmeticWorldObject, new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPrefixRef", (Type[])null), new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				PatchMethods.SpawnArgumentWriteMode = "ref __0";
				Log.LogInfo((object)"Patched SpawnCosmeticWorldObject rarity argument using ref __0 mode.");
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Could not patch SpawnCosmeticWorldObject with ref __0 rarity writer; falling back to __args-only mode. Rarity weights may only be logged/requested if this fallback is used. {arg}");
				harmony.Patch((MethodBase)spawnCosmeticWorldObject, new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPrefixArgsOnly", (Type[])null), new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				PatchMethods.SpawnArgumentWriteMode = "__args fallback";
				Log.LogInfo((object)"Patched SpawnCosmeticWorldObject using __args fallback mode.");
			}
		}

		private static void LogSpawnMethodSignature(MethodInfo method)
		{
			if (!RarityDiagnosticsEnabled || method == null)
			{
				return;
			}
			try
			{
				string text = string.Join(", ", from p in method.GetParameters()
					select p.ParameterType.FullName + " " + p.Name);
				ConsoleInfo("SpawnCosmeticWorldObject signature: " + method.DeclaringType?.FullName + "." + method.Name + "(" + text + ")");
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)$"Failed to log SpawnCosmeticWorldObject signature. {arg}");
				}
			}
		}

		private static MethodInfo GetIteratorMoveNext(MethodInfo method)
		{
			Type type = method?.GetCustomAttribute<IteratorStateMachineAttribute>()?.StateMachineType;
			if (!(type == null))
			{
				return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
			}
			return null;
		}

		internal static void ActivatePendingSettingsForLevel()
		{
			ConfigSettings pendingConfig;
			lock (SettingsLock)
			{
				pendingConfig = _pendingConfig;
			}
			int num = Random.Range(pendingConfig.MinBoxes, pendingConfig.MaxBoxes + 1);
			lock (SettingsLock)
			{
				_activeSettings = new LevelSettings(pendingConfig, num);
				_actualBoxesThisLevel = 0;
			}
			ConsoleInfo($"Cosmetic box target for this level: {num} box(es) from range {pendingConfig.MinBoxes}-{pendingConfig.MaxBoxes}.");
			ConsoleInfo("Cosmetic-box level settings active: " + Describe(ActiveSettings));
		}

		private static void ActivatePendingSettingsForStartup()
		{
			ConfigSettings pendingConfig;
			lock (SettingsLock)
			{
				pendingConfig = _pendingConfig;
			}
			lock (SettingsLock)
			{
				_activeSettings = new LevelSettings(pendingConfig, pendingConfig.MinBoxes);
				_actualBoxesThisLevel = 0;
			}
			ConsoleInfo("Cosmetic-box startup settings active: " + Describe(ActiveSettings));
		}

		private static void ApplyPendingSettingsToCurrentLevelKeepTarget(string reason)
		{
			lock (SettingsLock)
			{
				ConfigSettings pendingConfig = _pendingConfig;
				int targetBoxes = _activeSettings.TargetBoxes;
				_activeSettings = new LevelSettings(pendingConfig, targetBoxes);
			}
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogInfo((object)("EvilTwins Mods applied live config update (" + reason + "): " + Describe(ActiveSettings)));
			}
		}

		internal static void RecordActualBoxSpawned(string rarityName, string raritySource)
		{
			int actualBoxesThisLevel;
			int targetBoxes;
			lock (SettingsLock)
			{
				_actualBoxesThisLevel++;
				actualBoxesThisLevel = _actualBoxesThisLevel;
				targetBoxes = _activeSettings.TargetBoxes;
			}
			string text = (string.IsNullOrWhiteSpace(raritySource) ? string.Empty : (" [" + raritySource + "]"));
			string text2 = (string.IsNullOrWhiteSpace(rarityName) ? "unknown rarity" : rarityName);
			ConsoleInfo($"Cosmetic box spawned: {actualBoxesThisLevel}/{targetBoxes} for this level. Rarity request: {text2}{text}.");
		}

		internal static bool NeedsGuaranteedBox()
		{
			GetGuaranteeState(out var settings, out var actual);
			if (settings.Enabled)
			{
				return actual < settings.TargetBoxes;
			}
			return false;
		}

		internal static void GetGuaranteeState(out LevelSettings settings, out int actual)
		{
			lock (SettingsLock)
			{
				settings = _activeSettings;
				actual = _actualBoxesThisLevel;
			}
		}

		internal static bool IsHostOrSingleplayer()
		{
			if (TryGetHostStatus(out var isHost))
			{
				return isHost;
			}
			return true;
		}

		internal static bool TryGetHostStatus(out bool isHost)
		{
			try
			{
				isHost = SemiFunc.IsMasterClientOrSingleplayer();
				return true;
			}
			catch
			{
				isHost = true;
				return false;
			}
		}

		internal static bool ShouldUseHostControlledConfig()
		{
			if (!HostAuthoritativeConfigEnabled)
			{
				return true;
			}
			if (TryGetHostStatus(out var isHost))
			{
				return isHost;
			}
			return true;
		}

		private static void LogHostAuthorityStatus(string reason)
		{
			if (_logHostAuthority != null && !_logHostAuthority.Value)
			{
				return;
			}
			try
			{
				bool isHost;
				bool flag = TryGetHostStatus(out isHost);
				if (HostAuthoritativeConfigEnabled)
				{
					if (!flag)
					{
						ConsoleInfo("Host authoritative config active (" + reason + "): host/client state is not initialized yet; startup config setup is allowed and runtime hooks will re-check later.");
					}
					else
					{
						ConsoleInfo(isHost ? ("Host authoritative config active (" + reason + "): you are host/singleplayer, so this config controls EvilTwins Mods gameplay settings.") : ("Host authoritative config active (" + reason + "): you are not host, so this local gameplay config is ignored. The host's EvilTwins Mods config controls the run."));
					}
				}
				else
				{
					ConsoleInfo("Host authoritative config disabled (" + reason + "): local config may apply wherever this client runs EvilTwins Mods patched code.");
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)$"Failed to log host-authority status. {arg}");
				}
			}
		}

		internal static void ConsoleInfo(string message)
		{
			string value = "[EvilTwins Mods] " + message;
			try
			{
				Console.WriteLine(value);
			}
			catch
			{
			}
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		private static void UpdatePendingSettings()
		{
			ConfigSettings pendingConfig = ReadSettingsFromConfig();
			lock (SettingsLock)
			{
				_pendingConfig = pendingConfig;
			}
			Log.LogInfo((object)($"EvilTwins Mods cosmetic-box config queued for next level: enabled={pendingConfig.Enabled}, min={pendingConfig.MinBoxes}, max={pendingConfig.MaxBoxes}, " + $"rarityWeightsEnabled={pendingConfig.RarityWeightsEnabled}, weights={pendingConfig.CommonWeight}/{pendingConfig.UncommonWeight}/{pendingConfig.RareWeight}/{pendingConfig.UltraRareWeight}"));
		}

		private static ConfigSettings ReadSettingsFromConfig()
		{
			return new ConfigSettings(_cosmeticBoxRangeEnabled.Value, _minBoxes.Value, _maxBoxes.Value, _rarityWeightsEnabled.Value, _commonWeight.Value, _uncommonWeight.Value, _rareWeight.Value, _ultraRareWeight.Value);
		}

		private static string Describe(LevelSettings settings)
		{
			if (!settings.Enabled)
			{
				return "cosmetic box range disabled";
			}
			string arg = (settings.RarityWeightsEnabled ? $"rarity weights enabled ({settings.CommonWeight}/{settings.UncommonWeight}/{settings.RareWeight}/{settings.UltraRareWeight})" : "rarity weights disabled");
			return $"target boxes={settings.TargetBoxes} from range {settings.MinBoxes}-{settings.MaxBoxes}, " + $"{arg}, loopMax={settings.LoopMax}, compensationLoopMax={settings.CompensationLoopMax}";
		}
	}
	internal readonly struct SpawnState
	{
		public int TargetAmountBefore { get; }

		public string RarityName { get; }

		public string RaritySource { get; }

		public SpawnState(int targetAmountBefore, string rarityName, string raritySource)
		{
			TargetAmountBefore = targetAmountBefore;
			RarityName = rarityName;
			RaritySource = raritySource;
		}
	}
	internal static class PatchMethods
	{
		private const int ForcedFailureRoll = 99999;

		private static readonly FieldInfo TargetAmountField = AccessTools.Field(typeof(ValuableDirector), "cosmeticWorldObjectTargetAmount");

		private static readonly FieldInfo CosmeticSetupsField = AccessTools.Field(typeof(ValuableDirector), "cosmeticWorldObjectSetups");

		private static bool _chanceGatePatchActive;

		private static bool _extractionLimitPatchActive;

		private static bool _suppressLoopsClampedGetPostfix;

		internal static bool ChanceGatePatchActive => _chanceGatePatchActive;

		internal static bool ExtractionLimitPatchActive => _extractionLimitPatchActive;

		internal static string SpawnArgumentWriteMode { get; set; } = "unpatched";

		internal static void ResetPatchState()
		{
			_chanceGatePatchActive = false;
			_extractionLimitPatchActive = false;
			_suppressLoopsClampedGetPostfix = false;
		}

		internal static void SetupHostPrefix(ValuableDirector __instance)
		{
			if (Plugin.ShouldUseHostControlledConfig())
			{
				Plugin.ActivatePendingSettingsForLevel();
				LogRaritySetupMapping(__instance);
			}
		}

		internal static IEnumerable<CodeInstruction> SetupHostMoveNextTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo methodInfo = AccessTools.Method(typeof(Random), "Range", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
			MethodInfo operand = AccessTools.Method(typeof(PatchMethods), "GuaranteedChanceRoll", (Type[])null, (Type[])null);
			List<int> list2 = new List<int>();
			for (int i = 0; i <= list.Count - 3; i++)
			{
				if (IsLdcI4(list[i], 0) && IsLdcI4(list[i + 1], 100) && CodeInstructionExtensions.Calls(list[i + 2], methodInfo))
				{
					list2.Add(i);
				}
			}
			if (list2.Count != 1)
			{
				_chanceGatePatchActive = false;
				string text = $"Expected exactly one cosmetic Random.Range(0,100) chance gate in SetupHost.MoveNext, found {list2.Count}; rolling back patches.";
				Plugin.Log.LogError((object)text);
				throw new InvalidOperationException(text);
			}
			list[list2[0] + 2].operand = operand;
			_chanceGatePatchActive = true;
			Plugin.Log.LogInfo((object)"Patched SetupHost.MoveNext cosmetic chance gate.");
			return list;
		}

		internal static IEnumerable<CodeInstruction> ExtractRpcTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo methodInfo = AccessTools.Method(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsClampedGet", (Type[])null, (Type[])null);
			MethodInfo operand = AccessTools.Method(typeof(PatchMethods), "CosmeticExtractionLoopLimit", (Type[])null, (Type[])null);
			List<int> list2 = new List<int>();
			for (int i = 0; i < list.Count; i++)
			{
				if (CodeInstructionExtensions.Calls(list[i], methodInfo))
				{
					list2.Add(i);
				}
			}
			if (list2.Count != 1)
			{
				_extractionLimitPatchActive = false;
				string text = $"Expected exactly one CosmeticWorldObjectLevelLoopsClampedGet call in CosmeticWorldObject.ExtractRPC, found {list2.Count}; rolling back patches.";
				Plugin.Log.LogError((object)text);
				throw new InvalidOperationException(text);
			}
			list[list2[0]].opcode = OpCodes.Call;
			list[list2[0]].operand = operand;
			_extractionLimitPatchActive = true;
			Plugin.Log.LogInfo((object)"Patched CosmeticWorldObject.ExtractRPC cosmetic extraction loop limit.");
			return list;
		}

		internal static int GuaranteedChanceRoll(int min, int max)
		{
			int result = Random.Range(min, max);
			if (!Plugin.ShouldUseHostControlledConfig())
			{
				return result;
			}
			Plugin.GetGuaranteeState(out var settings, out var actual);
			if (!settings.Enabled)
			{
				return result;
			}
			if (actual >= settings.TargetBoxes)
			{
				return 99999;
			}
			return min;
		}

		internal static int CosmeticExtractionLoopLimit(ValuableDirector director)
		{
			int num = 0;
			try
			{
				if ((Object)(object)director != (Object)null)
				{
					_suppressLoopsClampedGetPostfix = true;
					num = director.CosmeticWorldObjectLevelLoopsClampedGet();
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to read original cosmetic extraction loop limit; using safe fallback. {arg}");
			}
			finally
			{
				_suppressLoopsClampedGetPostfix = false;
			}
			int targetAmount = GetTargetAmount(director);
			if (targetAmount <= 0)
			{
				return num;
			}
			return Math.Max(num, targetAmount - 1);
		}

		internal static void LoopsGetPostfix(ref int __result)
		{
			if (Plugin.ShouldUseHostControlledConfig())
			{
				LevelSettings activeSettings = Plugin.ActiveSettings;
				if (activeSettings.Enabled)
				{
					__result = activeSettings.LoopMax;
				}
			}
		}

		internal static void LoopsClampedGetPostfix(ref int __result)
		{
			if (!_suppressLoopsClampedGetPostfix && Plugin.ShouldUseHostControlledConfig())
			{
				LevelSettings activeSettings = Plugin.ActiveSettings;
				if (activeSettings.Enabled)
				{
					__result = activeSettings.CompensationLoopMax;
				}
			}
		}

		internal static void SpawnCosmeticWorldObjectPrefixRef(ValuableDirector __instance, ref object __0, object[] __args, out SpawnState __state)
		{
			object obj = __0;
			if (obj == null && __args != null && __args.Length != 0)
			{
				obj = __args[0];
			}
			__state = CreateInitialSpawnState(__instance, obj);
			if (TryChooseReplacementRarity(__instance, obj, __args, out var selectedRarity, out var selectedSource))
			{
				__0 = selectedRarity;
				if (__args != null && __args.Length != 0)
				{
					__args[0] = selectedRarity;
				}
				__state = new SpawnState(__state.TargetAmountBefore, RarityDisplayName(selectedRarity), selectedSource + ", ref __0");
			}
		}

		internal static void SpawnCosmeticWorldObjectPrefixArgsOnly(ValuableDirector __instance, object[] __args, out SpawnState __state)
		{
			object cosmeticRarity = ((__args != null && __args.Length != 0) ? __args[0] : null);
			__state = CreateInitialSpawnState(__instance, cosmeticRarity);
			if (TryChooseReplacementRarity(__instance, cosmeticRarity, __args, out var selectedRarity, out var selectedSource))
			{
				if (__args != null && __args.Length != 0)
				{
					__args[0] = selectedRarity;
				}
				__state = new SpawnState(__state.TargetAmountBefore, RarityDisplayName(selectedRarity), selectedSource + ", __args fallback");
			}
		}

		private static SpawnState CreateInitialSpawnState(ValuableDirector instance, object cosmeticRarity)
		{
			int targetAmount = GetTargetAmount(instance);
			string rarityName = "unknown rarity";
			try
			{
				if (cosmeticRarity != null)
				{
					rarityName = RarityDisplayName(cosmeticRarity);
				}
			}
			catch
			{
				rarityName = "unknown rarity";
			}
			return new SpawnState(targetAmount, rarityName, "vanilla");
		}

		private static bool TryChooseReplacementRarity(ValuableDirector instance, object cosmeticRarity, object[] args, out object selectedRarity, out string selectedSource)
		{
			selectedRarity = cosmeticRarity;
			selectedSource = "vanilla";
			if (!Plugin.ShouldUseHostControlledConfig())
			{
				return false;
			}
			LevelSettings activeSettings = Plugin.ActiveSettings;
			if (!activeSettings.Enabled)
			{
				return false;
			}
			try
			{
				if (args == null || args.Length < 3)
				{
					return false;
				}
				int[] array = args[1] as int[];
				List<ValuableVolume>[] array2 = args[2] as List<ValuableVolume>[];
				if (cosmeticRarity == null || array == null || array2 == null)
				{
					return false;
				}
				Type type = cosmeticRarity.GetType();
				if (Plugin.NeedsGuaranteedBox())
				{
					if (activeSettings.RarityWeightsEnabled && TryPickWeightedAvailableRarity(instance, type, array, array2, activeSettings, out var rarity))
					{
						selectedRarity = rarity;
						selectedSource = "weighted";
						return true;
					}
					if (!HasAvailableVolume(instance, cosmeticRarity, array, array2) && TryFindAvailableRarity(instance, type, array, array2, out var rarity2))
					{
						selectedRarity = rarity2;
						selectedSource = "fallback available volume";
						return true;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"SpawnCosmeticWorldObjectPrefix failed; leaving vanilla spawn logic unchanged. {arg}");
			}
			return false;
		}

		internal static void SpawnCosmeticWorldObjectPostfix(ValuableDirector __instance, SpawnState __state)
		{
			if (!Plugin.ShouldUseHostControlledConfig() || !Plugin.ActiveSettings.Enabled)
			{
				return;
			}
			try
			{
				if (GetTargetAmount(__instance) > __state.TargetAmountBefore)
				{
					Plugin.RecordActualBoxSpawned(__state.RarityName, __state.RaritySource);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"SpawnCosmeticWorldObjectPostfix failed. {arg}");
			}
		}

		private static int GetTargetAmount(ValuableDirector director)
		{
			if ((Object)(object)director == (Object)null || TargetAmountField == null || TargetAmountField.FieldType != typeof(int))
			{
				return 0;
			}
			try
			{
				return (int)TargetAmountField.GetValue(director);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to read cosmeticWorldObjectTargetAmount. {arg}");
				return 0;
			}
		}

		private static void LogRaritySetupMapping(ValuableDirector director)
		{
			if (!Plugin.RarityDiagnosticsEnabled)
			{
				return;
			}
			try
			{
				if (!TryGetCosmeticSetups(director, out var setups))
				{
					Plugin.ConsoleInfo("Rarity diagnostic: could not read cosmeticWorldObjectSetups.");
					return;
				}
				List<string> list = new List<string>();
				for (int i = 0; i < setups.Count; i++)
				{
					object obj = setups[i];
					if (obj == null)
					{
						list.Add($"index {i}: null");
						continue;
					}
					string text = obj.ToString();
					Object val = (Object)((obj is Object) ? obj : null);
					if (val != null && !string.IsNullOrWhiteSpace(val.name))
					{
						text = val.name;
					}
					int volumeIndex;
					string text2 = (TryReadSetupVolume(obj, out volumeIndex) ? $"volume {volumeIndex}" : "volume unknown");
					list.Add($"index {i}: {text} ({obj.GetType().Name}, {text2})");
				}
				Plugin.ConsoleInfo("Rarity diagnostic mapping: " + string.Join(" | ", list));
				Plugin.ConsoleInfo("Rarity argument write mode: " + SpawnArgumentWriteMode);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Failed to log rarity setup mapping. {arg}");
			}
		}

		private static bool TryPickWeightedAvailableRarity(ValuableDirector director, Type rarityType, int[] volumeIndex, List<ValuableVolume>[] volumes, LevelSettings settings, out object rarity)
		{
			rarity = null;
			if (!TryGetCosmeticSetups(director, out var setups) || rarityType == null || !rarityType.IsEnum)
			{
				return false;
			}
			List<object> list = new List<object>();
			List<int> list2 = new List<int>();
			int num = 0;
			for (int i = 0; i < setups.Count && i < 4; i++)
			{
				int rarityWeight = settings.GetRarityWeight(i);
				if (rarityWeight > 0 && Enum.IsDefined(rarityType, i) && setups[i] != null)
				{
					object obj = Enum.ToObject(rarityType, i);
					if (HasAvailableVolume(director, obj, volumeIndex, volumes))
					{
						list.Add(obj);
						list2.Add(rarityWeight);
						num += rarityWeight;
					}
				}
			}
			if (list.Count == 0 || num <= 0)
			{
				return false;
			}
			int num2 = Random.Range(0, num);
			for (int j = 0; j < list.Count; j++)
			{
				if (num2 < list2[j])
				{
					rarity = list[j];
					if (Plugin.RarityDiagnosticsEnabled)
					{
						Plugin.ConsoleInfo($"Weighted rarity selected {RarityDisplayName(rarity)} from {list.Count} available weighted candidate(s).");
					}
					return true;
				}
				num2 -= list2[j];
			}
			rarity = list[list.Count - 1];
			if (Plugin.RarityDiagnosticsEnabled)
			{
				Plugin.ConsoleInfo($"Weighted rarity selected {RarityDisplayName(rarity)} from {list.Count} available weighted candidate(s).");
			}
			return true;
		}

		private static string RarityDisplayName(object rarity)
		{
			if (rarity == null)
			{
				return "unknown rarity";
			}
			try
			{
				int num = Convert.ToInt32(rarity);
				string text = rarity.ToString();
				if (!string.IsNullOrWhiteSpace(text) && text != num.ToString())
				{
					return $"{text} (index {num})";
				}
				return num switch
				{
					0 => "Common (index 0)", 
					1 => "Uncommon (index 1)", 
					2 => "Rare/Epic (index 2)", 
					3 => "Ultra Rare/Legendary (index 3)", 
					_ => $"Rarity index {num}", 
				};
			}
			catch
			{
				return rarity.ToString();
			}
		}

		private static bool TryFindAvailableRarity(ValuableDirector director, Type rarityType, int[] volumeIndex, List<ValuableVolume>[] volumes, out object rarity)
		{
			rarity = null;
			if (!TryGetCosmeticSetups(director, out var setups) || rarityType == null || !rarityType.IsEnum)
			{
				return false;
			}
			for (int i = 0; i < setups.Count; i++)
			{
				if (Enum.IsDefined(rarityType, i) && setups[i] != null)
				{
					object obj = Enum.ToObject(rarityType, i);
					if (HasAvailableVolume(director, obj, volumeIndex, volumes))
					{
						rarity = obj;
						return true;
					}
				}
			}
			return false;
		}

		private static bool TryGetVolumeIndexForRarity(ValuableDirector director, object rarity, out int volumeIndex)
		{
			volumeIndex = 0;
			if (!TryGetCosmeticSetups(director, out var setups) || rarity == null)
			{
				return false;
			}
			int num = Convert.ToInt32(rarity);
			if (num < 0 || num >= setups.Count)
			{
				return false;
			}
			object obj = setups[num];
			if (obj == null)
			{
				return false;
			}
			return TryReadSetupVolume(obj, out volumeIndex);
		}

		private static bool TryGetCosmeticSetups(ValuableDirector director, out IList setups)
		{
			setups = null;
			if ((Object)(object)director == (Object)null || CosmeticSetupsField == null)
			{
				return false;
			}
			try
			{
				setups = CosmeticSetupsField.GetValue(director) as IList;
				return setups != null;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to read cosmeticWorldObjectSetups. {arg}");
				return false;
			}
		}

		private static bool TryReadSetupVolume(object setup, out int volumeIndex)
		{
			volumeIndex = 0;
			try
			{
				Type type = setup.GetType();
				FieldInfo fieldInfo = AccessTools.Field(type, "volume");
				if (fieldInfo != null)
				{
					volumeIndex = Convert.ToInt32(fieldInfo.GetValue(setup));
					return true;
				}
				PropertyInfo propertyInfo = AccessTools.Property(type, "volume");
				if (propertyInfo != null)
				{
					volumeIndex = Convert.ToInt32(propertyInfo.GetValue(setup, null));
					return true;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to read cosmetic setup volume. {arg}");
			}
			return false;
		}

		private static bool HasAvailableVolume(ValuableDirector director, object rarity, int[] volumeIndex, List<ValuableVolume>[] volumes)
		{
			if (TryGetVolumeIndexForRarity(director, rarity, out var volumeIndex2) && volumes != null && volumeIndex != null && volumeIndex2 >= 0 && volumeIndex2 < volumes.Length && volumeIndex2 < volumeIndex.Length && volumes[volumeIndex2] != null && volumeIndex[volumeIndex2] >= 0)
			{
				return volumeIndex[volumeIndex2] < volumes[volumeIndex2].Count;
			}
			return false;
		}

		private static bool IsLdcI4(CodeInstruction instruction, int expected)
		{
			if (instruction.opcode == OpCodes.Ldc_I4_M1)
			{
				return expected == -1;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_0)
			{
				return expected == 0;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_1)
			{
				return expected == 1;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_2)
			{
				return expected == 2;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_3)
			{
				return expected == 3;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_4)
			{
				return expected == 4;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_5)
			{
				return expected == 5;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_6)
			{
				return expected == 6;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_7)
			{
				return expected == 7;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_8)
			{
				return expected == 8;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_S || instruction.opcode == OpCodes.Ldc_I4)
			{
				return Convert.ToInt32(instruction.operand) == expected;
			}
			return false;
		}
	}
	internal enum ShopItemCategory
	{
		Unknown,
		Upgrade,
		Weapon,
		Drone,
		HealthPack,
		Crystal,
		Consumable
	}
	internal static class ShopTweaks
	{
		private static readonly FieldInfo UpgradeValueIncreaseField = AccessTools.Field(typeof(ShopManager), "upgradeValueIncrease");

		private static readonly FieldInfo HealthPackValueIncreaseField = AccessTools.Field(typeof(ShopManager), "healthPackValueIncrease");

		private static readonly FieldInfo CrystalValueIncreaseField = AccessTools.Field(typeof(ShopManager), "crystalValueIncrease");

		private static bool _enabled;

		private static bool _logDetails;

		private static int _upgradePricePercent = 100;

		private static int _weaponPricePercent = 100;

		private static int _dronePricePercent = 100;

		private static int _healthPackPricePercent = 100;

		private static int _crystalPricePercent = 100;

		private static bool _disableMultiplayerScaling;

		private static bool _disableOwnedUpgradeScaling;

		private static bool _disableLevelProgressScaling;

		private static int _multiplayerScalingPerExtraPlayerPercent = 10;

		private static int _ownedUpgradeScalingPercent = 100;

		private static int _levelProgressScalingPercent = 100;

		internal static void Configure(bool enabled, bool logDetails, int upgradePricePercent, int weaponPricePercent, int dronePricePercent, int healthPackPricePercent, int crystalPricePercent, bool disableMultiplayerScaling, bool disableOwnedUpgradeScaling, bool disableLevelProgressScaling, int multiplayerScalingPerExtraPlayerPercent, int ownedUpgradeScalingPercent, int levelProgressScalingPercent)
		{
			_enabled = enabled;
			_logDetails = logDetails;
			_upgradePricePercent = Mathf.Clamp(upgradePricePercent, 1, 500);
			_weaponPricePercent = Mathf.Clamp(weaponPricePercent, 1, 500);
			_dronePricePercent = Mathf.Clamp(dronePricePercent, 1, 500);
			_healthPackPricePercent = Mathf.Clamp(healthPackPricePercent, 1, 500);
			_crystalPricePercent = Mathf.Clamp(crystalPricePercent, 1, 500);
			_disableMultiplayerScaling = disableMultiplayerScaling;
			_disableOwnedUpgradeScaling = disableOwnedUpgradeScaling;
			_disableLevelProgressScaling = disableLevelProgressScaling;
			_multiplayerScalingPerExtraPlayerPercent = Mathf.Clamp(multiplayerScalingPerExtraPlayerPercent, -90, 500);
			_ownedUpgradeScalingPercent = Mathf.Clamp(ownedUpgradeScalingPercent, 0, 500);
			_levelProgressScalingPercent = Mathf.Clamp(levelProgressScalingPercent, 0, 500);
			Plugin.ConsoleInfo($"Shop Tweaks config: enabled={_enabled}, prices upgrade/weapon/drone/health/crystal={_upgradePricePercent}/{_weaponPricePercent}/{_dronePricePercent}/{_healthPackPricePercent}/{_crystalPricePercent}, " + $"scaling multiplayerOff={_disableMultiplayerScaling}, ownedOff={_disableOwnedUpgradeScaling}, levelOff={_disableLevelProgressScaling}.");
		}

		internal static void TryPatch(Harmony harmony)
		{
			PatchOptional(harmony, typeof(ShopManager), "UpgradeValueGet", "UpgradeValueGetPrefix");
			PatchOptional(harmony, typeof(ShopManager), "HealthPackValueGet", "HealthPackValueGetPrefix");
			PatchOptional(harmony, typeof(ShopManager), "CrystalValueGet", "CrystalValueGetPrefix");
		}

		private static void PatchOptional(Harmony harmony, Type type, string methodName, string prefix = null, string postfix = null)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)("Shop Tweaks: could not find " + type.Name + "." + methodName + "; that shop feature will not be patched."));
			}
			else
			{
				HarmonyMethod val = (string.IsNullOrWhiteSpace(prefix) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ShopTweaks), prefix, (Type[])null));
				HarmonyMethod val2 = (string.IsNullOrWhiteSpace(postfix) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ShopTweaks), postfix, (Type[])null));
				harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)("Shop Tweaks patched " + type.Name + "." + methodName + "."));
			}
		}

		internal static bool UpgradeValueGetPrefix(float _value, Item item, ref float __result, ShopManager __instance)
		{
			if (!_enabled || !Plugin.ShouldUseHostControlledConfig())
			{
				return true;
			}
			try
			{
				ShopItemCategory shopItemCategory = DetectCategory(item, ShopItemCategory.Upgrade);
				float num = _value;
				int playerCount = GetPlayerCount();
				if (!_disableMultiplayerScaling)
				{
					float num2 = (float)_multiplayerScalingPerExtraPlayerPercent / 100f * (float)Mathf.Max(playerCount - 1, 0);
					num2 = Mathf.Max(num2, -0.9f);
					num += num * num2;
				}
				if (!_disableOwnedUpgradeScaling)
				{
					float num3 = ReadFloatField(UpgradeValueIncreaseField, __instance, 0.5f) * ((float)_ownedUpgradeScalingPercent / 100f);
					int purchasedCount = GetPurchasedCount(item);
					num += num * num3 * (float)purchasedCount;
				}
				int num4 = PercentForUpgradeCategory(shopItemCategory);
				num = (__result = ApplyPercentAndClamp(num, num4));
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Shop price: {ItemName(item)} category={shopItemCategory} base={_value:0.##} players={playerCount} percent={num4}% => {num:0}.");
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Shop Tweaks upgrade price patch failed; using vanilla price. {arg}");
				return true;
			}
		}

		internal static bool HealthPackValueGetPrefix(float _value, ref float __result, ShopManager __instance)
		{
			if (!_enabled || !Plugin.ShouldUseHostControlledConfig())
			{
				return true;
			}
			try
			{
				float num = _value;
				int playerCount = GetPlayerCount();
				if (!_disableMultiplayerScaling)
				{
					float num2 = (float)_multiplayerScalingPerExtraPlayerPercent / 100f * (float)Mathf.Max(playerCount - 1, 0);
					num2 = Mathf.Max(num2, -0.9f);
					num += num * num2;
				}
				if (!_disableLevelProgressScaling)
				{
					float num3 = ReadFloatField(HealthPackValueIncreaseField, __instance, 0.05f) * ((float)_levelProgressScalingPercent / 100f);
					num += num * num3 * (float)GetLevelsCompleted();
				}
				num = (__result = ApplyPercentAndClamp(num, _healthPackPricePercent));
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Shop price: HealthPack base={_value:0.##} players={playerCount} percent={_healthPackPricePercent}% => {num:0}.");
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Shop Tweaks health pack price patch failed; using vanilla price. {arg}");
				return true;
			}
		}

		internal static bool CrystalValueGetPrefix(float _value, ref float __result, ShopManager __instance)
		{
			if (!_enabled || !Plugin.ShouldUseHostControlledConfig())
			{
				return true;
			}
			try
			{
				float num = _value;
				if (!_disableLevelProgressScaling)
				{
					float num2 = ReadFloatField(CrystalValueIncreaseField, __instance, 0.2f) * ((float)_levelProgressScalingPercent / 100f);
					num += num * num2 * (float)GetLevelsCompleted();
				}
				num = (__result = ApplyPercentAndClamp(num, _crystalPricePercent));
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Shop price: EnergyCrystal base={_value:0.##} percent={_crystalPricePercent}% => {num:0}.");
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Shop Tweaks crystal price patch failed; using vanilla price. {arg}");
				return true;
			}
		}

		private static int PercentForUpgradeCategory(ShopItemCategory category)
		{
			return category switch
			{
				ShopItemCategory.Weapon => _weaponPricePercent, 
				ShopItemCategory.Drone => _dronePricePercent, 
				_ => _upgradePricePercent, 
			};
		}

		private static float ApplyPercentAndClamp(float value, int percent)
		{
			return Mathf.Max(Mathf.Ceil(value * ((float)percent / 100f)), 1f);
		}

		private static int GetPurchasedCount(Item item)
		{
			if ((Object)(object)item == (Object)null || (Object)(object)StatsManager.instance == (Object)null)
			{
				return 0;
			}
			try
			{
				return StatsManager.instance.GetItemsUpgradesPurchased(((Object)item).name);
			}
			catch
			{
				return 0;
			}
		}

		private static int GetPlayerCount()
		{
			try
			{
				if ((Object)(object)GameDirector.instance != (Object)null && GameDirector.instance.PlayerList != null)
				{
					return Mathf.Max(GameDirector.instance.PlayerList.Count, 1);
				}
			}
			catch
			{
			}
			return 1;
		}

		private static int GetLevelsCompleted()
		{
			try
			{
				if ((Object)(object)RunManager.instance != (Object)null)
				{
					return Mathf.Max(RunManager.instance.levelsCompleted, 0);
				}
			}
			catch
			{
			}
			return 0;
		}

		private static float ReadFloatField(FieldInfo field, object instance, float fallback)
		{
			try
			{
				if (field == null || instance == null)
				{
					return fallback;
				}
				return Convert.ToSingle(field.GetValue(instance));
			}
			catch
			{
				return fallback;
			}
		}

		private static ShopItemCategory DetectCategory(Item item, ShopItemCategory fallback)
		{
			if ((Object)(object)item == (Object)null)
			{
				return fallback;
			}
			string text = ReadItemTypeText(item).ToLowerInvariant();
			string text2 = ItemName(item).ToLowerInvariant();
			string source = (text + " " + text2).Trim();
			if (ContainsAny(source, "item upgrade", "upgrade player", "player upgrade"))
			{
				return ShopItemCategory.Upgrade;
			}
			if (ContainsAny(source, "drone"))
			{
				return ShopItemCategory.Drone;
			}
			if (ContainsAny(source, "weapon", "gun", "rifle", "shotgun", "pistol", "grenade", "sword", "bat", "melee", "taser", "zap", "orb", "mine"))
			{
				return ShopItemCategory.Weapon;
			}
			if (ContainsAny(source, "health", "heal", "med", "medicine", "first aid"))
			{
				return ShopItemCategory.HealthPack;
			}
			if (ContainsAny(source, "crystal", "energy"))
			{
				return ShopItemCategory.Crystal;
			}
			if (ContainsAny(source, "consume", "consumable"))
			{
				return ShopItemCategory.Consumable;
			}
			if (ContainsAny(source, "upgrade"))
			{
				return ShopItemCategory.Upgrade;
			}
			return fallback;
		}

		private static string ReadItemTypeText(Item item)
		{
			try
			{
				Type type = ((object)item).GetType();
				FieldInfo fieldInfo = AccessTools.Field(type, "itemType") ?? AccessTools.Field(type, "type");
				if (fieldInfo != null)
				{
					return fieldInfo.GetValue(item)?.ToString() ?? string.Empty;
				}
				PropertyInfo propertyInfo = AccessTools.Property(type, "itemType") ?? AccessTools.Property(type, "type");
				if (propertyInfo != null)
				{
					return propertyInfo.GetValue(item, null)?.ToString() ?? string.Empty;
				}
			}
			catch
			{
			}
			return string.Empty;
		}

		private static bool ContainsAny(string source, params string[] keywords)
		{
			if (string.IsNullOrWhiteSpace(source))
			{
				return false;
			}
			foreach (string value in keywords)
			{
				if (source.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		private static string ItemName(Item item)
		{
			try
			{
				return ((Object)item).name ?? string.Empty;
			}
			catch
			{
				return ((item != null) ? ((Object)item).name : null) ?? string.Empty;
			}
		}

		private static string ItemKey(Item item)
		{
			return ItemName(item).Replace("(Clone)", string.Empty).Replace("(clone)", string.Empty).Trim();
		}
	}
	internal sealed class UpgradeShareState
	{
		public string SteamID { get; }

		public string PlayerName { get; }

		public int ViewID { get; }

		public string ItemName { get; }

		public Dictionary<string, int> LevelsBefore { get; }

		public UpgradeShareState(string steamID, string playerName, int viewID, string itemName, Dictionary<string, int> levelsBefore)
		{
			SteamID = steamID;
			PlayerName = playerName;
			ViewID = viewID;
			ItemName = itemName;
			LevelsBefore = levelsBefore ?? new Dictionary<string, int>();
		}
	}
	internal static class UpgradeSharing
	{
		private static readonly FieldInfo StatsDictionaryField = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

		private static readonly FieldInfo ItemToggleField = AccessTools.Field(typeof(ItemUpgrade), "itemToggle");

		private static readonly FieldInfo ItemAttributesField = AccessTools.Field(typeof(ItemUpgrade), "itemAttributes");

		private static bool _enabled;

		private static bool _logDetails;

		private static bool _matchHighestTeamLevel = true;

		private static bool _healHealthUpgrades;

		private static int _sharingDepth;

		internal static void Configure(bool enabled, bool logDetails, bool matchHighestTeamLevel, bool healHealthUpgrades)
		{
			_enabled = enabled;
			_logDetails = logDetails;
			_matchHighestTeamLevel = matchHighestTeamLevel;
			_healHealthUpgrades = healHealthUpgrades;
		}

		internal static void TryPatch(Harmony harmony)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0064: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(ItemUpgrade), "PlayerUpgrade", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogError((object)"Upgrade Sharing: ItemUpgrade.PlayerUpgrade was not found; upgrade sharing disabled.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(UpgradeSharing), "PlayerUpgradePrefix", (Type[])null), new HarmonyMethod(typeof(UpgradeSharing), "PlayerUpgradePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)"Upgrade Sharing patched ItemUpgrade.PlayerUpgrade.");
		}

		internal static void PlayerUpgradePrefix(ItemUpgrade __instance, out UpgradeShareState __state)
		{
			__state = null;
			if (!_enabled || _sharingDepth > 0 || !Plugin.ShouldUseHostControlledConfig())
			{
				return;
			}
			try
			{
				int viewID;
				PlayerAvatar upgradePlayer = GetUpgradePlayer(__instance, out viewID);
				string playerSteamID = GetPlayerSteamID(upgradePlayer);
				string playerName = GetPlayerName(upgradePlayer, playerSteamID);
				if ((Object)(object)upgradePlayer == (Object)null || string.IsNullOrWhiteSpace(playerSteamID))
				{
					if (_logDetails)
					{
						Plugin.ConsoleInfo("Upgrade sharing: ItemUpgrade.PlayerUpgrade fired, but buyer could not be identified.");
					}
					return;
				}
				string upgradeItemName = GetUpgradeItemName(__instance);
				Dictionary<string, int> dictionary = SnapshotPlayerUpgradeLevels(playerSteamID);
				__state = new UpgradeShareState(playerSteamID, playerName, viewID, upgradeItemName, dictionary);
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Upgrade sharing: {playerName} is using '{upgradeItemName}'. Snapshot contains {dictionary.Count} upgrade stat(s).");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Upgrade Sharing prefix failed. {arg}");
			}
		}

		internal static void PlayerUpgradePostfix(UpgradeShareState __state)
		{
			if (!_enabled || _sharingDepth > 0 || !Plugin.ShouldUseHostControlledConfig() || __state == null)
			{
				return;
			}
			try
			{
				Dictionary<string, int> dictionary = SnapshotPlayerUpgradeLevels(__state.SteamID);
				int num = 0;
				foreach (KeyValuePair<string, int> item in dictionary)
				{
					string key = item.Key;
					int value = item.Value;
					int value2 = 0;
					__state.LevelsBefore.TryGetValue(key, out value2);
					if (value > value2)
					{
						int gained = value - value2;
						num++;
						ShareUpgradeToTeam(__state, key, gained, value);
					}
				}
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Upgrade sharing: finished checking '{__state.ItemName}' for {__state.PlayerName}. Changed upgrade stats={num}.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Upgrade Sharing postfix failed. {arg}");
			}
		}

		private static void ShareUpgradeToTeam(UpgradeShareState context, string upgradeKey, int gained, int buyerLevelAfter)
		{
			if (string.IsNullOrWhiteSpace(upgradeKey) || gained <= 0)
			{
				return;
			}
			List<PlayerAvatar> allPlayersSafe = GetAllPlayersSafe();
			if (allPlayersSafe.Count <= 1)
			{
				if (_logDetails)
				{
					Plugin.ConsoleInfo($"Upgrade sharing: {upgradeKey} +{gained}, but there are no other players to share with.");
				}
				return;
			}
			int num = (_matchHighestTeamLevel ? Math.Max(buyerLevelAfter, GetTeamMaxLevel(upgradeKey, allPlayersSafe)) : buyerLevelAfter);
			string text = CleanUpgradeName(upgradeKey);
			int num2 = 0;
			int num3 = 0;
			EnterSharing();
			try
			{
				foreach (PlayerAvatar item in allPlayersSafe)
				{
					string playerSteamID = GetPlayerSteamID(item);
					string playerName = GetPlayerName(item, playerSteamID);
					if ((Object)(object)item == (Object)null || string.IsNullOrWhiteSpace(playerSteamID))
					{
						num3++;
						continue;
					}
					if (playerSteamID == context.SteamID)
					{
						num3++;
						continue;
					}
					int playerUpgradeLevel = GetPlayerUpgradeLevel(upgradeKey, playerSteamID);
					int num4 = (_matchHighestTeamLevel ? (num - playerUpgradeLevel) : gained);
					if (num4 <= 0)
					{
						num3++;
					}
					else if (TrySendVanillaUpgradeRpc(playerSteamID, text, num4))
					{
						num2++;
						if (_logDetails)
						{
							Plugin.ConsoleInfo($"Upgrade sharing: sent {upgradeKey} ({text}) +{num4} to {playerName}; level {playerUpgradeLevel} -> {playerUpgradeLevel + num4}.");
						}
						if (_healHealthUpgrades && IsHealthUpgrade(upgradeKey))
						{
							TryHealSharedHealthUpgrade(item, num4);
						}
					}
					else
					{
						num3++;
						Plugin.Log.LogWarning((object)$"Upgrade sharing: failed to send {upgradeKey} +{num4} to {playerName}.");
					}
				}
			}
			finally
			{
				ExitSharing();
			}
			Plugin.ConsoleInfo($"Upgrade sharing: {context.PlayerName} gained {upgradeKey} +{gained}. Shared to {num2} player(s), skipped {num3}. Target level={num}.");
		}

		private static Dictionary<string, int> SnapshotPlayerUpgradeLevels(string steamID)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			IDictionary statsDictionary = GetStatsDictionary();
			if (statsDictionary == null || string.IsNullOrWhiteSpace(steamID))
			{
				return dictionary;
			}
			foreach (DictionaryEntry item in statsDictionary)
			{
				string key = item.Key as string;
				if (LooksLikePlayerUpgradeKey(key) && item.Value is IDictionary dictionary2)
				{
					dictionary[key] = ReadDictionaryInt(dictionary2, steamID, 0);
				}
			}
			return dictionary;
		}

		private static int GetTeamMaxLevel(string upgradeKey, List<PlayerAvatar> players)
		{
			int num = 0;
			foreach (PlayerAvatar player in players)
			{
				string playerSteamID = GetPlayerSteamID(player);
				if (!((Object)(object)player == (Object)null) && !string.IsNullOrWhiteSpace(playerSteamID))
				{
					num = Math.Max(num, GetPlayerUpgradeLevel(upgradeKey, playerSteamID));
				}
			}
			return num;
		}

		private static int GetPlayerUpgradeLevel(string upgradeKey, string steamID)
		{
			IDictionary statsDictionary = GetStatsDictionary();
			if (statsDictionary == null || string.IsNullOrWhiteSpace(upgradeKey) || string.IsNullOrWhiteSpace(steamID))
			{
				return 0;
			}
			if (!(statsDictionary[upgradeKey] is IDictionary dictionary))
			{
				return 0;
			}
			return ReadDictionaryInt(dictionary, steamID, 0);
		}

		private static IDictionary GetStatsDictionary()
		{
			try
			{
				if ((Object)(object)StatsManager.instance == (Object)null || StatsDictionaryField == null)
				{
					return null;
				}
				return StatsDictionaryField.GetValue(StatsManager.instance) as IDictionary;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Upgrade sharing: failed to read StatsManager.dictionaryOfDictionaries. {arg}");
				return null;
			}
		}

		private static int ReadDictionaryInt(IDictionary dictionary, string key, int fallback)
		{
			try
			{
				if (dictionary == null || !dictionary.Contains(key))
				{
					return fallback;
				}
				object obj = dictionary[key];
				return (obj == null) ? fallback : Convert.ToInt32(obj);
			}
			catch
			{
				return fallback;
			}
		}

		private static PlayerAvatar GetUpgradePlayer(ItemUpgrade instance, out int viewID)
		{
			viewID = 0;
			try
			{
				object obj = ((ItemToggleField != null) ? ItemToggleField.GetValue(instance) : ReadMember(instance, "itemToggle"));
				if (obj == null)
				{
					return null;
				}
				object obj2 = ReadMember(obj, "toggleState");
				if (obj2 != null && !Convert.ToBoolean(obj2))
				{
					return null;
				}
				object obj3 = ReadMember(obj, "playerTogglePhotonID");
				if (obj3 == null)
				{
					return null;
				}
				viewID = Convert.ToInt32(obj3);
				return SemiFunc.PlayerAvatarGetFromPhotonID(viewID);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Upgrade sharing: failed to identify upgrade player. " + ex.Message));
				return null;
			}
		}

		private static string GetUpgradeItemName(ItemUpgrade instance)
		{
			try
			{
				object obj = ReadMember((ItemAttributesField != null) ? ItemAttributesField.GetValue(instance) : ReadMember(instance, "itemAttributes"), "item");
				Object val = (Object)((obj is Object) ? obj : null);
				if (val != null && !string.IsNullOrWhiteSpace(val.name))
				{
					return val.name;
				}
				return ReadMember(obj, "name")?.ToString() ?? "unknown upgrade item";
			}
			catch
			{
				return "unknown upgrade item";
			}
		}

		private static object ReadMember(object instance, string memberName)
		{
			if (instance == null || string.IsNullOrWhiteSpace(memberName))
			{
				return null;
			}
			try
			{
				Type type = instance.GetType();
				FieldInfo fieldInfo = AccessTools.Field(type, memberName);
				if (fieldInfo != null)
				{
					return fieldInfo.GetValue(instance);
				}
				PropertyInfo propertyInfo = AccessTools.Property(type, memberName);
				if (propertyInfo != null)
				{
					return propertyInfo.GetValue(instance, null);
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool TrySendVanillaUpgradeRpc(string steamID, string cleanUpgradeName, int difference)
		{
			if (string.IsNullOrWhiteSpace(steamID) || string.IsNullOrWhiteSpace(cleanUpgradeName) || difference <= 0)
			{
				return false;
			}
			try
			{
				if ((Object)(object)PunManager.instance == (Object)null)
				{
					return false;
				}
				Type type = AccessTools.TypeByName("Photon.Pun.PhotonView");
				Type rpcTargetType = AccessTools.TypeByName("Photon.Pun.RpcTarget");
				if (type == null || rpcTargetType == null)
				{
					Plugin.Log.LogWarning((object)"Upgrade sharing: Photon.Pun types were not found.");
					return false;
				}
				Component instance = (Component)(object)PunManager.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				Component component = instance.GetComponent(type);
				if ((Object)(object)component == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Upgrade sharing: PhotonView was not found on PunManager.");
					return false;
				}
				object obj = Enum.Parse(rpcTargetType, "All");
				MethodInfo methodInfo = type.GetMethods().FirstOrDefault(delegate(MethodInfo m)
				{
					if (m.Name != "RPC")
					{
						return false;
					}
					ParameterInfo[] parameters = m.GetParameters();
					return parameters.Length == 3 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == rpcTargetType && parameters[2].ParameterType == typeof(object[]);
				});
				if (methodInfo == null)
				{
					Plugin.Log.LogWarning((object)"Upgrade sharing: PhotonView.RPC(string, RpcTarget, object[]) was not found.");
					return false;
				}
				methodInfo.Invoke(component, new object[3]
				{
					"TesterUpgradeCommandRPC",
					obj,
					new object[3] { steamID, cleanUpgradeName, difference }
				});
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Upgrade sharing: TesterUpgradeCommandRPC failed. {arg}");
				return false;
			}
		}

		private static void TryHealSharedHealthUpgrade(PlayerAvatar player, int difference)
		{
			try
			{
				if (!((Object)(object)player == (Object)null) && !((Object)(object)player.playerHealth == (Object)null) && difference > 0)
				{
					player.playerHealth.HealOther(20 * difference, false);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Upgrade sharing: failed to heal shared health upgrade recipient. " + ex.Message));
			}
		}

		private static string GetPlayerSteamID(PlayerAvatar player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return string.Empty;
			}
			string[] array = new string[8] { "steamID", "steamId", "SteamID", "SteamId", "steamID64", "steamId64", "playerSteamID", "playerSteamId" };
			string[] array2 = array;
			foreach (string memberName in array2)
			{
				object obj = ReadMember(player, memberName);
				if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
				{
					return obj.ToString();
				}
			}
			try
			{
				object instance = ReadMember(player, "playerSteam");
				array2 = array;
				foreach (string memberName2 in array2)
				{
					object obj2 = ReadMember(instance, memberName2);
					if (obj2 != null && !string.IsNullOrWhiteSpace(obj2.ToString()))
					{
						return obj2.ToString();
					}
				}
			}
			catch
			{
			}
			return string.Empty;
		}

		private static string GetPlayerName(PlayerAvatar player, string fallback)
		{
			if ((Object)(object)player == (Object)null)
			{
				if (!string.IsNullOrWhiteSpace(fallback))
				{
					return fallback;
				}
				return "unknown player";
			}
			string[] array = new string[6] { "playerName", "playerNameText", "PlayerName", "name", "nickName", "nickname" };
			foreach (string memberName in array)
			{
				object obj = ReadMember(player, memberName);
				if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()))
				{
					return obj.ToString();
				}
			}
			try
			{
				object name = ((Object)player).name;
				if (name != null && !string.IsNullOrWhiteSpace(name.ToString()))
				{
					return name.ToString();
				}
			}
			catch
			{
			}
			if (!string.IsNullOrWhiteSpace(fallback))
			{
				return fallback;
			}
			return "unknown player";
		}

		private static List<PlayerAvatar> GetAllPlayersSafe()
		{
			try
			{
				return SemiFunc.PlayerGetAll() ?? new List<PlayerAvatar>();
			}
			catch
			{
				return new List<PlayerAvatar>();
			}
		}

		private static bool LooksLikePlayerUpgradeKey(string key)
		{
			if (!string.IsNullOrWhiteSpace(key))
			{
				return key.StartsWith("playerUpgrade", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool IsHealthUpgrade(string key)
		{
			return string.Equals(key, "playerUpgradeHealth", StringComparison.OrdinalIgnoreCase);
		}

		private static string CleanUpgradeName(string upgradeKey)
		{
			if (string.IsNullOrWhiteSpace(upgradeKey))
			{
				return upgradeKey;
			}
			if (upgradeKey.StartsWith("playerUpgrade", StringComparison.OrdinalIgnoreCase) && upgradeKey.Length > "playerUpgrade".Length)
			{
				return upgradeKey.Substring("playerUpgrade".Length);
			}
			return upgradeKey;
		}

		private static void EnterSharing()
		{
			_sharingDepth++;
		}

		private static void ExitSharing()
		{
			_sharingDepth = Math.Max(0, _sharingDepth - 1);
		}
	}
}