Decompiled source of All Shop Items In Level v1.8.0

plugins/REPO_All_Shop_Items_in_Level.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[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 REPO_All_Shop_Items_in_Level
{
	public class UsedVolumeTracker : MonoBehaviour
	{
	}
	public class SpawnedItemTracker : MonoBehaviour
	{
	}
	[BepInPlugin("REPO_All_Shop_Items_in_Level", "ALL Shop Items spawn in Level", "1.8.0")]
	[BepInProcess("REPO.exe")]
	public class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(EnemyParent), "Despawn")]
		private class DespawnPatch
		{
			private static int GetSpawnValuableCurrent(EnemyParent ep)
			{
				try
				{
					object? obj = AccessTools.Field(typeof(EnemyParent), "Enemy")?.GetValue(ep);
					Enemy obj2 = (Enemy)((obj is Enemy) ? obj : null);
					object? obj3 = AccessTools.Field(typeof(Enemy), "Health")?.GetValue(obj2);
					EnemyHealth obj4 = (EnemyHealth)((obj3 is EnemyHealth) ? obj3 : null);
					FieldInfo fieldInfo = AccessTools.Field(typeof(EnemyHealth), "spawnValuableCurrent");
					return (!(fieldInfo == null)) ? ((int)fieldInfo.GetValue(obj4)) : 0;
				}
				catch (Exception ex)
				{
					DebugLog("Error reading spawnValuableCurrent: " + ex.Message);
					return 0;
				}
			}

			private static itemType GetLootTypeForEnemy(EnemyParent ep)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_007b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				if (!EnableEnemyLootTables.Value)
				{
					return (itemType)8;
				}
				string text = ((Object)ep).name?.ToLower() ?? "";
				if (text.Contains("gnome") || text.Contains("small"))
				{
					return (itemType)3;
				}
				if (text.Contains("large") || text.Contains("boss"))
				{
					return (itemType)0;
				}
				return (itemType)8;
			}

			private static void Prefix(EnemyParent __instance, out int __state)
			{
				__state = GetSpawnValuableCurrent(__instance);
			}

			private static void Postfix(EnemyParent __instance, int __state)
			{
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				//IL_006c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0119: Unknown result type (might be due to invalid IL or missing references)
				//IL_011e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0123: Unknown result type (might be due to invalid IL or missing references)
				//IL_0128: Unknown result type (might be due to invalid IL or missing references)
				//IL_012a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0159: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0177: Unknown result type (might be due to invalid IL or missing references)
				if (!SemiFunc.IsMasterClientOrSingleplayer() || !SpawnHealthPacksFromEnemies.Value || GetSpawnValuableCurrent(__instance) <= __state || Random.Range(0f, 100f) > HealthPackDropChance.Value)
				{
					return;
				}
				itemType lootTypeForEnemy = GetLootTypeForEnemy(__instance);
				if ((!GetRandomItemOfType(lootTypeForEnemy, out var item) && !GetRandomItemOfType((itemType)8, out item)) || (Object)(object)item == (Object)null)
				{
					return;
				}
				try
				{
					object? obj = AccessTools.Field(typeof(EnemyParent), "Enemy")?.GetValue(__instance);
					Enemy val = (Enemy)((obj is Enemy) ? obj : null);
					if ((Object)(object)val == (Object)null)
					{
						return;
					}
					Transform val2 = (Object.op_Implicit((Object)(object)val.CustomValuableSpawnTransform) ? val.CustomValuableSpawnTransform : val.CenterTransform);
					if (!((Object)(object)val2 == (Object)null))
					{
						Vector3 val3 = val2.position + Vector3.up;
						if (!IsPositionValid(val3))
						{
							DebugLog("Enemy " + ((Object)__instance).name + " has invalid spawn position, skipping drop");
							return;
						}
						SpawnItem(item, val3, Quaternion.identity);
						DebugLog($"Dropped {((Object)item).name} from {((Object)__instance).name} ({lootTypeForEnemy})");
					}
				}
				catch (Exception ex)
				{
					DebugLog("Error spawning enemy loot: " + ex.Message);
				}
			}
		}

		[HarmonyPatch]
		private class ValuableDirector_Spawn_Patch
		{
			private static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(ValuableDirector), "Spawn", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(ValuableDirector), "SpawnValuable", (Type[])null, (Type[])null);
			}

			[HarmonyPrefix]
			private static void Prefix(object[] __args)
			{
				ValuableVolume val = __args.OfType<ValuableVolume>().FirstOrDefault();
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).gameObject.AddComponent<UsedVolumeTracker>();
				}
			}
		}

		internal static ManualLogSource Logger;

		internal static ConfigEntry<bool> DebugMode;

		internal static ConfigEntry<bool> SaveAllOnLevelComplete;

		internal static ConfigEntry<bool> SpawnUpgradeItems;

		internal static ConfigEntry<float> UpgradeItemSpawnChance;

		internal static ConfigEntry<bool> MapHideUpgradeItems;

		internal static ConfigEntry<bool> UseShopPriceForUpgradeItems;

		internal static ConfigEntry<bool> SpawnDroneItems;

		internal static ConfigEntry<float> DroneItemSpawnChance;

		internal static ConfigEntry<bool> MapHideDroneItems;

		internal static ConfigEntry<bool> UseShopPriceForDroneItems;

		internal static ConfigEntry<bool> SpawnOtherShopItems;

		internal static ConfigEntry<float> OtherShopItemSpawnChance;

		internal static ConfigEntry<bool> MapHideOtherShopItems;

		internal static ConfigEntry<bool> UseShopPriceForOtherItems;

		internal static ConfigEntry<bool> SpawnHealthPacksFromEnemies;

		internal static ConfigEntry<float> HealthPackDropChance;

		internal static ConfigEntry<float> MinItemValue;

		internal static ConfigEntry<float> MaxItemValue;

		internal static ConfigEntry<int> MaxItemsPerLevel;

		internal static ConfigEntry<int> MinItemsPerLevel;

		internal static ConfigEntry<bool> EnableLevelScaling;

		internal static ConfigEntry<float> LevelScalingMultiplier;

		internal static ConfigEntry<bool> AnnounceRareItems;

		internal static ConfigEntry<float> AnnounceValueThreshold;

		internal static ConfigEntry<bool> EnableEnemyLootTables;

		internal static List<ConfigEntry<bool>> DisallowedUpgradeItems;

		internal static List<ConfigEntry<bool>> DisallowedDroneItems;

		internal static List<ConfigEntry<bool>> DisallowedOtherItems;

		private static readonly HashSet<int> KnownSpecialTypes = new HashSet<int> { 0, 3, 8 };

		private static Dictionary<string, Item> _cachedItemDictionary;

		private static List<Item> _cachedUpgrades;

		private static List<Item> _cachedDrones;

		private static List<Item> _cachedOtherItems;

		private static float _lastItemRefreshTime;

		private static bool _isSpawning = false;

		public static Plugin Instance { get; private set; }

		internal static void DebugLog(string message)
		{
			if (DebugMode != null && DebugMode.Value)
			{
				Logger.LogInfo((object)("[DEBUG] " + message));
			}
		}

		private void Awake()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Expected O, but got Unknown
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Expected O, but got Unknown
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Expected O, but got Unknown
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Expected O, but got Unknown
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Expected O, but got Unknown
			//IL_0385: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Expected O, but got Unknown
			//IL_03ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Expected O, but got Unknown
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_0421: Expected O, but got Unknown
			//IL_0474: Unknown result type (might be due to invalid IL or missing references)
			//IL_047e: Expected O, but got Unknown
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			Logger.LogInfo((object)"Plugin REPO_All_Shop_Items_in_Level is loaded!");
			Harmony val = new Harmony("REPO_All_Shop_Items_in_Level");
			val.PatchAll(typeof(Plugin));
			val.PatchAll(typeof(DespawnPatch));
			val.PatchAll(typeof(ValuableDirector_Spawn_Patch));
			Logger.LogInfo((object)"Harmony patches applied!");
			DebugMode = ((BaseUnityPlugin)this).Config.Bind<bool>("0_Debug", "DebugMode", false, "Enable verbose debug logging.");
			SaveAllOnLevelComplete = ((BaseUnityPlugin)this).Config.Bind<bool>("0_Debug", "SaveAllOnLevelComplete", true, "If true, all spawned items still in the level are saved when you complete a level.");
			SpawnUpgradeItems = ((BaseUnityPlugin)this).Config.Bind<bool>("1_UpgradeItems", "SpawnUpgradeItems", true, "Whether upgrade items can spawn in levels");
			MapHideUpgradeItems = ((BaseUnityPlugin)this).Config.Bind<bool>("1_UpgradeItems", "MapHideShopUpgradeItems", false, "(Client) Whether upgrade items are hidden on the map");
			UpgradeItemSpawnChance = ((BaseUnityPlugin)this).Config.Bind<float>("1_UpgradeItems", "UpgradeItemSpawnChance", 2.5f, new ConfigDescription("% chance for an upgrade item to spawn", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			UseShopPriceForUpgradeItems = ((BaseUnityPlugin)this).Config.Bind<bool>("1_UpgradeItems", "UseShopPriceForItemSelection", true, "If ON: Cheaper upgrade items appear more often.");
			SpawnDroneItems = ((BaseUnityPlugin)this).Config.Bind<bool>("2_DroneItems", "SpawnDroneItems", true, "Whether drone items can spawn in levels");
			MapHideDroneItems = ((BaseUnityPlugin)this).Config.Bind<bool>("2_DroneItems", "MapHideDroneItems", false, "(Client) Whether drone items are hidden on the map");
			DroneItemSpawnChance = ((BaseUnityPlugin)this).Config.Bind<float>("2_DroneItems", "DroneItemsSpawnChance", 0.95f, new ConfigDescription("% chance for a drone item to spawn", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			UseShopPriceForDroneItems = ((BaseUnityPlugin)this).Config.Bind<bool>("2_DroneItems", "UseShopPriceForItemSelection", true, "If ON: Cheaper drone items appear more often.");
			SpawnOtherShopItems = ((BaseUnityPlugin)this).Config.Bind<bool>("3_OtherShopItems", "SpawnOtherShopItems", true, "Whether other shop items can spawn in levels");
			MapHideOtherShopItems = ((BaseUnityPlugin)this).Config.Bind<bool>("3_OtherShopItems", "MapHideOtherShopItems", false, "(Client) Whether other shop items are hidden on the map");
			OtherShopItemSpawnChance = ((BaseUnityPlugin)this).Config.Bind<float>("3_OtherShopItems", "OtherShopItemSpawnChance", 1.5f, new ConfigDescription("% chance for other shop items to spawn", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			UseShopPriceForOtherItems = ((BaseUnityPlugin)this).Config.Bind<bool>("3_OtherShopItems", "UseShopPriceForItemSelection", true, "If ON: Cheaper items appear more often.");
			SpawnHealthPacksFromEnemies = ((BaseUnityPlugin)this).Config.Bind<bool>("4_HealthPacks", "SpawnHealthPacksFromEnemies", true, "Whether health packs can spawn when enemies die");
			HealthPackDropChance = ((BaseUnityPlugin)this).Config.Bind<float>("4_HealthPacks", "HealthPackDropChance", 100f, new ConfigDescription("% chance for a health pack to drop when an enemy dies", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			MinItemValue = ((BaseUnityPlugin)this).Config.Bind<float>("5_Filters", "MinItemValue", 0f, new ConfigDescription("Minimum item value for spawning (0 = disabled)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 999999f), Array.Empty<object>()));
			MaxItemValue = ((BaseUnityPlugin)this).Config.Bind<float>("5_Filters", "MaxItemValue", 0f, new ConfigDescription("Maximum item value for spawning (0 = disabled)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 999999f), Array.Empty<object>()));
			MaxItemsPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("5_Filters", "MaxItemsPerLevel", 0, new ConfigDescription("Maximum items to spawn per level (0 = unlimited)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 999), Array.Empty<object>()));
			MinItemsPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("5_Filters", "MinItemsPerLevel", 0, new ConfigDescription("Minimum items guaranteed to spawn per level (0 = disabled)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 999), Array.Empty<object>()));
			EnableLevelScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("6_LevelScaling", "EnableLevelScaling", true, "Spawn chances scale up as you progress through floors");
			LevelScalingMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("6_LevelScaling", "LevelScalingMultiplier", 0.1f, new ConfigDescription("Bonus multiplier per floor completed (0.1 = +10% per floor)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			AnnounceRareItems = ((BaseUnityPlugin)this).Config.Bind<bool>("7_Announce", "AnnounceRareItems", true, "Log to console when an expensive item spawns");
			AnnounceValueThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("7_Announce", "AnnounceValueThreshold", 5000f, new ConfigDescription("Min item value to trigger announce", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 999999f), Array.Empty<object>()));
			EnableEnemyLootTables = ((BaseUnityPlugin)this).Config.Bind<bool>("8_EnemyLoot", "EnableEnemyLootTables", true, "Specific enemy types drop specific item types instead of always health packs");
			DisallowedUpgradeItems = new List<ConfigEntry<bool>>();
			DisallowedDroneItems = new List<ConfigEntry<bool>>();
			DisallowedOtherItems = new List<ConfigEntry<bool>>();
			((MonoBehaviour)this).StartCoroutine(InitializeItemListsWhenReady());
		}

		private IEnumerator InitializeItemListsWhenReady()
		{
			float startTime = Time.time;
			float timeout = 30f;
			while ((Object)(object)StatsManager.instance == (Object)null || StatsManager.instance.itemDictionary == null || StatsManager.instance.itemDictionary.Count == 0)
			{
				if (Time.time - startTime > timeout)
				{
					Logger.LogError((object)"StatsManager failed to initialize within timeout - item filtering disabled");
					yield break;
				}
				yield return null;
			}
			Logger.LogInfo((object)"Initializing per-item disable lists");
			_cachedItemDictionary = StatsManager.instance.itemDictionary;
			foreach (Item item in _cachedItemDictionary.Values)
			{
				string itemName = ((Object)item).name;
				itemType itemType = item.itemType;
				itemType val = itemType;
				itemType val2 = val;
				if ((int)val2 != 0)
				{
					if ((int)val2 == 3)
					{
						ConfigEntry<bool> upgradeEntry = ((BaseUnityPlugin)this).Config.Bind<bool>("9_DisallowedItems_Upgrades", itemName, false, new ConfigDescription("Disable '" + itemName + "' from spawning in levels", (AcceptableValueBase)null, Array.Empty<object>()));
						if (upgradeEntry.Value)
						{
							DisallowedUpgradeItems.Add(upgradeEntry);
						}
					}
					else if (!KnownSpecialTypes.Contains((int)item.itemType) && (Object)(object)item.value != (Object)null && item.value.valueMin > 0f)
					{
						ConfigEntry<bool> otherEntry = ((BaseUnityPlugin)this).Config.Bind<bool>("9_DisallowedItems_OtherShop", itemName, false, new ConfigDescription("Disable '" + itemName + "' from spawning in levels", (AcceptableValueBase)null, Array.Empty<object>()));
						if (otherEntry.Value)
						{
							DisallowedOtherItems.Add(otherEntry);
						}
					}
				}
				else
				{
					ConfigEntry<bool> droneEntry = ((BaseUnityPlugin)this).Config.Bind<bool>("9_DisallowedItems_Drones", itemName, false, new ConfigDescription("Disable '" + itemName + "' from spawning in levels", (AcceptableValueBase)null, Array.Empty<object>()));
					if (droneEntry.Value)
					{
						DisallowedDroneItems.Add(droneEntry);
					}
				}
			}
			Logger.LogInfo((object)$"Initialized disallows: Upgrades={DisallowedUpgradeItems.Count} Drones={DisallowedDroneItems.Count} Other={DisallowedOtherItems.Count}");
			RefreshCachedItemLists();
		}

		private static void RefreshCachedItemLists()
		{
			if (StatsManager.instance?.itemDictionary != null)
			{
				_cachedItemDictionary = StatsManager.instance.itemDictionary;
				_cachedUpgrades = GetCandidates((Item i) => (int)i.itemType == 3);
				_cachedDrones = GetCandidates((Item i) => (int)i.itemType == 0);
				_cachedOtherItems = GetCandidates((Item i) => !KnownSpecialTypes.Contains((int)i.itemType));
				_lastItemRefreshTime = Time.time;
				DebugLog($"Cache refreshed: {_cachedUpgrades.Count} upgrades, {_cachedDrones.Count} drones, {_cachedOtherItems.Count} other");
			}
		}

		private static bool PassesValueFilter(Item item)
		{
			float num = (((Object)(object)item.value != (Object)null) ? item.value.valueMin : 0f);
			if (MinItemValue.Value > 0f && num < MinItemValue.Value)
			{
				return false;
			}
			if (MaxItemValue.Value > 0f && num > MaxItemValue.Value)
			{
				return false;
			}
			return true;
		}

		private static bool IsItemAllowed(Item item, List<ConfigEntry<bool>> disallowedList)
		{
			string key = ((Object)item).name;
			return !disallowedList.Any((ConfigEntry<bool> entry) => ((ConfigEntryBase)entry).Definition.Key == key && entry.Value);
		}

		private static List<Item> GetCandidates(Func<Item, bool> typeFilter)
		{
			if (StatsManager.instance?.itemDictionary == null)
			{
				return new List<Item>();
			}
			return (from i in StatsManager.instance.itemDictionary.Values.Where(typeFilter)
				where (Object)(object)i.value != (Object)null && i.value.valueMin > 0f
				select i).Where(PassesValueFilter).ToList();
		}

		private static Item WeightedOrRandom(List<Item> list, bool usePrice)
		{
			if (list == null || list.Count == 0)
			{
				return null;
			}
			if (!usePrice)
			{
				return list[Random.Range(0, list.Count)];
			}
			float num = list.Sum((Item i) => 1f / Mathf.Max(0.01f, i.value.valueMin));
			if (num <= 0f || float.IsNaN(num) || float.IsInfinity(num))
			{
				return list[Random.Range(0, list.Count)];
			}
			int num2 = 0;
			while (num2++ < 50)
			{
				float num3 = Random.Range(0f, num);
				foreach (Item item in list)
				{
					num3 -= 1f / Mathf.Max(0.01f, item.value.valueMin);
					if (num3 <= 0f)
					{
						return item;
					}
				}
			}
			return list[0];
		}

		private static float GetScaledChance(float baseChance)
		{
			if (!EnableLevelScaling.Value)
			{
				return baseChance;
			}
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				return baseChance;
			}
			int levelsCompleted = RunManager.instance.levelsCompleted;
			return Mathf.Min(baseChance * (1f + (float)levelsCompleted * LevelScalingMultiplier.Value), 100f);
		}

		private static bool IsPositionValid(Vector3 pos)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (float.IsNaN(pos.x) || float.IsNaN(pos.y) || float.IsNaN(pos.z))
			{
				return false;
			}
			if (pos == Vector3.zero)
			{
				return false;
			}
			return true;
		}

		private static bool IsVolumeStillValid(ValuableVolume volume)
		{
			if ((Object)(object)volume == (Object)null)
			{
				return false;
			}
			if ((Object)(object)volume == (Object)null || (Object)(object)((Component)volume).gameObject == (Object)null)
			{
				return false;
			}
			if (!((Component)volume).gameObject.activeInHierarchy)
			{
				return false;
			}
			return true;
		}

		private static void AnnounceItem(Item item, Vector3 position)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			if (AnnounceRareItems.Value && !((Object)(object)item?.value == (Object)null) && !(item.value.valueMin < AnnounceValueThreshold.Value))
			{
				Logger.LogInfo((object)$"[RARE SPAWN] {((Object)item).name} (value ~{item.value.valueMin}) at {position}");
			}
		}

		private static bool GetRandomItemOfType(itemType type, out Item item, bool usePriceWeighting = true)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			item = null;
			List<Item> list = null;
			List<ConfigEntry<bool>> disallowedList = null;
			if ((int)type != 0)
			{
				if ((int)type != 3)
				{
					if ((int)type == 8)
					{
						list = GetCandidates((Item i) => (int)i.itemType == 8);
						disallowedList = null;
						usePriceWeighting = true;
					}
					else
					{
						list = _cachedOtherItems;
						disallowedList = DisallowedOtherItems;
						usePriceWeighting = UseShopPriceForOtherItems.Value;
					}
				}
				else
				{
					list = _cachedUpgrades;
					disallowedList = DisallowedUpgradeItems;
					usePriceWeighting = UseShopPriceForUpgradeItems.Value;
				}
			}
			else
			{
				list = _cachedDrones;
				disallowedList = DisallowedDroneItems;
				usePriceWeighting = UseShopPriceForDroneItems.Value;
			}
			if (list == null || list.Count == 0)
			{
				return false;
			}
			if (disallowedList != null && disallowedList.Count > 0)
			{
				list = list.Where((Item i) => IsItemAllowed(i, disallowedList)).ToList();
				if (list.Count == 0)
				{
					return false;
				}
			}
			if (Time.time - _lastItemRefreshTime > 60f)
			{
				RefreshCachedItemLists();
			}
			item = WeightedOrRandom(list, usePriceWeighting);
			return (Object)(object)item != (Object)null;
		}

		private static GameObject SpawnItem(Item item, Vector3 position, Quaternion rotation)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)item == (Object)null || item.prefab == null)
			{
				DebugLog("Spawn failed: null item or prefab");
				return null;
			}
			if (!IsPositionValid(position))
			{
				DebugLog($"Spawn of {((Object)item).name} rejected: bad position {position}");
				return null;
			}
			if (_isSpawning)
			{
				DebugLog("Spawn of " + ((Object)item).name + " blocked: already inside a spawn call");
				return null;
			}
			_isSpawning = true;
			try
			{
				string text = "Items/" + ((Object)item).name;
				GameObject val;
				if (SemiFunc.IsMultiplayer())
				{
					if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom)
					{
						DebugLog("Spawn of " + ((Object)item).name + " blocked: not in a Photon room");
						return null;
					}
					val = PhotonNetwork.InstantiateRoomObject(text, position, rotation, (byte)0, (object[])null);
				}
				else
				{
					val = Object.Instantiate<GameObject>(((PrefabRef<GameObject>)(object)item.prefab).Prefab, position, rotation);
				}
				if ((Object)(object)val != (Object)null)
				{
					val.AddComponent<SpawnedItemTracker>();
					AnnounceItem(item, position);
					DebugLog($"Spawned {((Object)item).name} at {position}");
				}
				return val;
			}
			catch (Exception ex)
			{
				DebugLog("Spawn of " + ((Object)item).name + " threw: " + ex.Message);
				return null;
			}
			finally
			{
				_isSpawning = false;
			}
		}

		private static bool HasValuablePropSwitch(ValuableVolume volume)
		{
			return (Object)(object)((Component)volume).GetComponentInParent<ValuablePropSwitch>() != (Object)null;
		}

		private static bool ShouldSpawnItem(ValuableVolume volume, out string category)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected I4, but got Unknown
			category = null;
			if (HasValuablePropSwitch(volume))
			{
				return false;
			}
			switch ((int)volume.VolumeType)
			{
			case 0:
				if (!SpawnUpgradeItems.Value)
				{
					return false;
				}
				category = "upgrade";
				return Random.Range(0f, 100f) <= GetScaledChance(UpgradeItemSpawnChance.Value);
			case 1:
				if (!SpawnDroneItems.Value)
				{
					return false;
				}
				category = "drone";
				return Random.Range(0f, 100f) <= GetScaledChance(DroneItemSpawnChance.Value);
			default:
				if (!SpawnOtherShopItems.Value)
				{
					return false;
				}
				category = "other";
				return Random.Range(0f, 100f) <= GetScaledChance(OtherShopItemSpawnChance.Value);
			}
		}

		private static bool RandomItemSpawn(ValuableVolume volume)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			if (!IsVolumeStillValid(volume))
			{
				return false;
			}
			if (!ShouldSpawnItem(volume, out var category))
			{
				return false;
			}
			Item item;
			bool flag = ((category == "upgrade") ? GetRandomItemOfType((itemType)3, out item) : ((!(category == "drone")) ? GetRandomItemOfType((itemType)999, out item) : GetRandomItemOfType((itemType)0, out item)));
			if (!flag || (Object)(object)item == (Object)null)
			{
				return false;
			}
			return (Object)(object)SpawnItem(item, ((Component)volume).transform.position, ((Component)volume).transform.rotation) != (Object)null;
		}

		[HarmonyPatch(typeof(ValuableDirector), "VolumesAndSwitchSetup")]
		[HarmonyPostfix]
		public static void ValuableDirector_VolumesAndSwitchSetup_Postfix()
		{
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			if (!SemiFunc.RunIsLevel() || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			RefreshCachedItemLists();
			List<ValuableVolume> list = (from v in Object.FindObjectsOfType<ValuableVolume>(false)
				where (Object)(object)((Component)v).gameObject.GetComponent<UsedVolumeTracker>() == (Object)null
				where !HasValuablePropSwitch(v)
				select v).ToList();
			Logger.LogInfo((object)$"Found {list.Count} volumes | upgrade:{GetScaledChance(UpgradeItemSpawnChance.Value):F1}% drone:{GetScaledChance(DroneItemSpawnChance.Value):F1}% other:{GetScaledChance(OtherShopItemSpawnChance.Value):F1}%");
			Logger.LogInfo((object)$"Item pool: {_cachedUpgrades?.Count ?? 0} upgrades | {_cachedDrones?.Count ?? 0} drones | {_cachedOtherItems?.Count ?? 0} other");
			int value = MaxItemsPerLevel.Value;
			int value2 = MinItemsPerLevel.Value;
			int num = 0;
			foreach (ValuableVolume item2 in list)
			{
				if (value > 0 && num >= value)
				{
					break;
				}
				if (RandomItemSpawn(item2))
				{
					num++;
				}
			}
			if (value2 > 0 && num < value2)
			{
				List<ValuableVolume> list2 = list.Where((ValuableVolume v) => (Object)(object)((Component)v).gameObject.GetComponent<UsedVolumeTracker>() == (Object)null).ToList();
				for (int num2 = list2.Count - 1; num2 > 0; num2--)
				{
					int num3 = Random.Range(0, num2 + 1);
					List<ValuableVolume> list3 = list2;
					int index = num2;
					int index2 = num3;
					ValuableVolume value3 = list2[num3];
					ValuableVolume value4 = list2[num2];
					list3[index] = value3;
					list2[index2] = value4;
				}
				foreach (ValuableVolume item3 in list2)
				{
					if (num >= value2 || (value > 0 && num >= value))
					{
						break;
					}
					if (IsVolumeStillValid(item3) && (GetRandomItemOfType((itemType)3, out var item) || GetRandomItemOfType((itemType)0, out item) || GetRandomItemOfType((itemType)999, out item)) && !((Object)(object)item == (Object)null))
					{
						Component val = (Component)(object)item3;
						if ((Object)(object)SpawnItem(item, val.transform.position, val.transform.rotation) != (Object)null)
						{
							num++;
						}
					}
				}
			}
			Logger.LogInfo((object)$"Spawned {num} items (min={value2} cap={value})");
		}

		[HarmonyPatch(typeof(Map), "AddCustom")]
		[HarmonyPostfix]
		public static void Map_AddCustom_Postfix(MapCustom mapCustom)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Expected I4, but got Unknown
			ItemAttributes val = default(ItemAttributes);
			if (SemiFunc.RunIsLevel() && !((Object)(object)mapCustom == (Object)null) && !((Object)(object)((Component)mapCustom).gameObject == (Object)null) && ((Component)mapCustom).gameObject.TryGetComponent<ItemAttributes>(ref val) && !((Object)(object)val?.item == (Object)null))
			{
				int num = (int)val.item.itemType;
				if ((num == 3 && MapHideUpgradeItems.Value) || (num == 0 && MapHideDroneItems.Value) || (!KnownSpecialTypes.Contains(num) && MapHideOtherShopItems.Value))
				{
					((Component)mapCustom).gameObject.SetActive(false);
					DebugLog("Hidden " + ((Object)val.item).name + " from map");
				}
			}
		}

		[HarmonyPatch(typeof(ExtractionPoint), "DestroyAllPhysObjectsInHaulList")]
		[HarmonyPostfix]
		public static void ExtractionPoint_DestroyAllPhysObjectsInHaulList_Postfix(ExtractionPoint __instance)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			SpawnedItemTracker[] array = Object.FindObjectsOfType<SpawnedItemTracker>(false);
			if (array.Length == 0)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>();
			int num = 0;
			SpawnedItemTracker[] array2 = array;
			foreach (SpawnedItemTracker spawnedItemTracker in array2)
			{
				GameObject gameObject = ((Component)spawnedItemTracker).gameObject;
				if ((Object)(object)gameObject == (Object)null)
				{
					continue;
				}
				bool flag = false;
				RoomVolumeCheck component = gameObject.GetComponent<RoomVolumeCheck>();
				if (component?.CurrentRooms != null && component.CurrentRooms.Any((RoomVolume r) => (Object)(object)r != (Object)null && r.Extraction))
				{
					flag = true;
				}
				PhysGrabObject component2 = gameObject.GetComponent<PhysGrabObject>();
				if ((Object)(object)component2 != (Object)null && component2.grabbed)
				{
					flag = true;
				}
				if (!flag)
				{
					continue;
				}
				ItemAttributes component3 = gameObject.GetComponent<ItemAttributes>();
				if ((Object)(object)component3?.item == (Object)null)
				{
					continue;
				}
				string name = ((Object)component3.item).name;
				if (hashSet.Contains(name))
				{
					continue;
				}
				if ((Object)(object)StatsManager.instance == (Object)null)
				{
					DebugLog("StatsManager is null during extraction, skipping " + name);
					continue;
				}
				Logger.LogInfo((object)("Saving extracted item: " + name));
				StatsManager.instance.ItemPurchase(name);
				hashSet.Add(name);
				if ((Object)(object)component2 != (Object)null)
				{
					component2.DestroyPhysGrabObject();
				}
				num++;
			}
			Logger.LogInfo((object)$"Saved {num} unique items from extraction");
		}

		[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
		[HarmonyPrefix]
		public static void RunManager_ChangeLevel_Prefix(RunManager __instance, bool _completedLevel, bool _levelFailed, int _changeLevelType)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || !_completedLevel || _levelFailed || !SaveAllOnLevelComplete.Value || _changeLevelType == 4)
			{
				return;
			}
			SpawnedItemTracker[] array = Object.FindObjectsOfType<SpawnedItemTracker>(false);
			if (array.Length == 0)
			{
				return;
			}
			Logger.LogInfo((object)$"Level complete. Fallback-saving {array.Length} spawned items...");
			HashSet<string> hashSet = new HashSet<string>();
			int num = 0;
			SpawnedItemTracker[] array2 = array;
			foreach (SpawnedItemTracker spawnedItemTracker in array2)
			{
				GameObject gameObject = ((Component)spawnedItemTracker).gameObject;
				if ((Object)(object)gameObject == (Object)null)
				{
					continue;
				}
				ItemAttributes component = gameObject.GetComponent<ItemAttributes>();
				if ((Object)(object)component?.item == (Object)null)
				{
					continue;
				}
				string name = ((Object)component.item).name;
				if (!hashSet.Contains(name))
				{
					PhysGrabObject component2 = gameObject.GetComponent<PhysGrabObject>();
					if ((Object)(object)component2 != (Object)null && component2.grabbed)
					{
						DebugLog("Saving carried item during level transition: " + name);
					}
					if ((Object)(object)StatsManager.instance == (Object)null)
					{
						DebugLog("StatsManager null during ChangeLevel save, skipping " + name);
						continue;
					}
					StatsManager.instance.ItemPurchase(name);
					hashSet.Add(name);
					num++;
				}
			}
			Logger.LogInfo((object)$"Fallback saved {num} unique items");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "REPO_All_Shop_Items_in_Level";

		public const string PLUGIN_NAME = "ALL Shop Items spawn in Level";

		public const string PLUGIN_VERSION = "1.8.0";
	}
}