Scry.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SoftReferenceableAssets;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("isimp")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 isimp")]
[assembly: AssemblyDescription("Browse every prefab in the game and preview it: look at creatures, items and pieces, play sounds and effects, fly projectiles and show status effects, without anything entering the world.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+771480105364c4addacd2cc32d4da17ed7fc2913")]
[assembly: AssemblyProduct("Scry")]
[assembly: AssemblyTitle("Scry")]
[assembly: AssemblyVersion("0.1.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 Scry
{
	internal static class CatalogBuilder
	{
		private sealed class Found
		{
			public GameObject Prefab;

			public PrefabTraits Traits;

			public string Token;

			public object Icon;

			public int ExtraLevels;

			public bool HasWear;

			public readonly HashSet<string> Users = new HashSet<string>(StringComparer.Ordinal);

			public readonly List<Origin> UserOrigins = new List<Origin>();

			public readonly HashSet<string> Components = new HashSet<string>(StringComparer.Ordinal);

			public readonly HashSet<string> Fields = new HashSet<string>(StringComparer.Ordinal);
		}

		internal struct ListOn
		{
			public string Field;

			public string Label;

			public EffectList List;
		}

		private static readonly Dictionary<Type, FieldInfo[]> EffectFieldsByType;

		private static readonly HashSet<string> FailedKinds;

		private static Dictionary<string, List<Recipe>> _recipes;

		private static readonly Dictionary<string, string> Localized;

		private static readonly Dictionary<Type, FieldInfo> NamingFields;

		private static readonly Dictionary<Type, (FieldInfo, string, string)[]> OwnListsByType;

		private static readonly Dictionary<Type, (FieldInfo, (FieldInfo, string, string)[])[]> NestedListsByType;

		static CatalogBuilder()
		{
			EffectFieldsByType = new Dictionary<Type, FieldInfo[]>();
			FailedKinds = new HashSet<string>();
			Localized = new Dictionary<string, string>(StringComparer.Ordinal);
			NamingFields = new Dictionary<Type, FieldInfo>();
			OwnListsByType = new Dictionary<Type, (FieldInfo, string, string)[]>();
			NestedListsByType = new Dictionary<Type, (FieldInfo, (FieldInfo, string, string)[])[]>();
			WorldCaches.Register("CatalogBuilder", Forget);
		}

		public static List<Entry> Build()
		{
			CatalogJob catalogJob = new CatalogJob();
			while (!catalogJob.Advance(double.MaxValue))
			{
			}
			if (catalogJob.Entries == null)
			{
				throw new InvalidOperationException(catalogJob.Failure ?? "the catalog could not be read");
			}
			return catalogJob.Entries;
		}

		internal static IEnumerator<string> Steps(CatalogJob job)
		{
			CatalogTiming.Reset();
			int collections = GC.CollectionCount(0);
			long started = CatalogTiming.Start();
			try
			{
				Compatibility.Check();
			}
			catch (Exception ex)
			{
				Faults.Tell("the startup check", ex);
			}
			CatalogTiming.Add("startup check", started);
			yield return "Reading the prefabs";
			started = CatalogTiming.Start();
			ZNetScene instance = ZNetScene.instance;
			Dictionary<string, Found> registered = new Dictionary<string, Found>(StringComparer.Ordinal);
			Dictionary<string, Found> effects = new Dictionary<string, Found>(StringComparer.Ordinal);
			EffectLinks.Clear();
			Localized.Clear();
			List<GameObject>[] array = new List<GameObject>[2] { instance.m_prefabs, instance.m_nonNetViewPrefabs };
			for (int i = 0; i < array.Length; i++)
			{
				foreach (GameObject item in array[i])
				{
					if (!((Object)(object)item == (Object)null) && !registered.ContainsKey(((Object)item).name))
					{
						registered[((Object)item).name] = new Found
						{
							Prefab = item
						};
					}
				}
			}
			List<GameObject> prefabs = registered.Values.Select((Found f) => f.Prefab).ToList();
			CatalogTiming.Add("setup", started);
			yield return "Reading recipes";
			started = CatalogTiming.Start();
			Knowledge.Begin();
			Relations.Begin(registered.Keys);
			IndexRecipes();
			CatalogTiming.Add("setup", started);
			yield return $"Reading prefabs: 0 of {registered.Count:N0}";
			List<Component> components = new List<Component>();
			List<Leftover> leftovers = new List<Leftover>();
			int read = 0;
			string progress = "";
			foreach (KeyValuePair<string, Found> item2 in registered)
			{
				job.Piece = item2.Key;
				ReadPrefab(item2.Key, item2.Value, effects, components, leftovers);
				int i = read + 1;
				read = i;
				if (i % 16 == 1)
				{
					progress = $"Reading prefabs: {read:N0} of {registered.Count:N0}";
				}
				yield return progress;
			}
			components.Clear();
			foreach (string item3 in Knowledge.Finish(prefabs))
			{
				yield return "Reading " + item3;
			}
			started = CatalogTiming.Start();
			List<Entry> entries = new List<Entry>();
			ObjectDB instance2 = ObjectDB.instance;
			if ((Object)(object)instance2 != (Object)null)
			{
				foreach (StatusEffect statusEffect in instance2.m_StatusEffects)
				{
					if (!((Object)(object)statusEffect == (Object)null))
					{
						try
						{
							Origin origin = Origins.StatusEffects.Of(((Object)statusEffect).name);
							string text = Localize(statusEffect.m_name);
							Gather(statusEffect, "status effect " + ((Object)statusEffect).name, origin, effects, "se:" + ((Object)statusEffect).name, (text.Length > 0) ? text : ((Object)statusEffect).name);
							Relations.ReadStatusEffect(statusEffect);
							entries.Add(new Entry
							{
								Name = ((Object)statusEffect).name,
								DisplayName = text,
								Kind = Kind.StatusEffect,
								Origin = origin,
								Source = statusEffect,
								Icon = statusEffect.m_icon,
								Components = new string[1] { ((object)statusEffect).GetType().Name },
								ModName = Knowledge.ModName(((Object)statusEffect).name)
							});
						}
						catch (Exception ex2)
						{
							Failed("status effect", ((Object)statusEffect).name, ex2);
						}
					}
				}
			}
			CatalogTiming.Add("status effects", started);
			yield return "Reading the interface's sounds";
			started = CatalogTiming.Start();
			GatherInterface(effects);
			CatalogTiming.Add("interface", started);
			yield return "Reading effects";
			HashSet<string> described = new HashSet<string>(StringComparer.Ordinal);
			int walked = 0;
			bool helpersRead = false;
			bool grew;
			do
			{
				grew = false;
				foreach (Found item4 in new List<Found>(effects.Values))
				{
					string name = ((Object)item4.Prefab).name;
					if (!registered.ContainsKey(name) && described.Add(name))
					{
						started = CatalogTiming.Start();
						components.Clear();
						Describe(item4, name, Provenance.Combine(item4.UserOrigins), effects, components);
						CatalogTiming.Add("describe effects", started);
						grew = true;
						int i = walked + 1;
						walked = i;
						if (i % 4 == 0)
						{
							yield return $"Reading effects: {walked:N0}";
						}
					}
				}
				if (grew || helpersRead)
				{
					continue;
				}
				helpersRead = true;
				List<Relations.Helper> helpers = new List<Relations.Helper>();
				Relations.TakeHelpers(helpers);
				for (int i2 = 0; i2 < helpers.Count; i2++)
				{
					started = CatalogTiming.Start();
					GatherHelper(helpers[i2], registered, effects, components);
					CatalogTiming.Add("helpers", started);
					if (i2 % 8 == 7)
					{
						yield return $"Reading what prefabs spawn: {i2 + 1:N0} of {helpers.Count:N0}";
					}
				}
				grew = true;
			}
			while (grew);
			components.Clear();
			int made = 0;
			int total = registered.Count + effects.Count;
			foreach (KeyValuePair<string, Found> item5 in registered)
			{
				MakeEntry(entries, item5.Key, item5.Value, effects, registeredOrigin: true);
				int i = made + 1;
				made = i;
				if (i % 16 == 0)
				{
					yield return $"Making entries: {made:N0} of about {total:N0}";
				}
			}
			foreach (KeyValuePair<string, Found> item6 in effects)
			{
				if (!registered.ContainsKey(item6.Key))
				{
					MakeEntry(entries, item6.Key, item6.Value, effects, registeredOrigin: false);
					int i = made + 1;
					made = i;
					if (i % 16 == 0)
					{
						yield return $"Making entries: {made:N0} of about {total:N0}";
					}
				}
			}
			yield return "Pairing leftovers";
			started = CatalogTiming.Start();
			try
			{
				Leftovers.Pair(entries, leftovers);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Scry could not pair what things leave behind, and leaves them unpaired: {arg}");
			}
			CatalogTiming.Add("leftovers", started);
			yield return "Linking entries";
			started = CatalogTiming.Start();
			LinkBook book = null;
			try
			{
				book = Relations.Finish(prefabs);
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogWarning((object)$"Scry could not link entries, and shows them without links: {arg2}");
			}
			CatalogTiming.Add("links finished", started);
			yield return "Linking entries";
			started = CatalogTiming.Start();
			IEnumerator<int> linking = null;
			try
			{
				if (book != null)
				{
					linking = book.ApplyInSteps(entries, 400).GetEnumerator();
				}
			}
			catch (Exception arg3)
			{
				Plugin.Log.LogWarning((object)$"Scry could not link entries, and shows them without links: {arg3}");
			}
			CatalogTiming.Add("links applied", started);
			while (linking != null)
			{
				started = CatalogTiming.Start();
				bool flag = false;
				try
				{
					flag = linking.MoveNext();
				}
				catch (Exception arg4)
				{
					Plugin.Log.LogWarning((object)$"Scry could not link all entries, and shows some without links: {arg4}");
				}
				CatalogTiming.Add("links applied", started);
				if (!flag)
				{
					break;
				}
				yield return $"Linking entries: {linking.Current:N0}";
			}
			yield return "Grouping entries";
			IEnumerator<int> grouping = null;
			try
			{
				grouping = Grouping.Apply(entries, 400).GetEnumerator();
			}
			catch (Exception arg5)
			{
				Plugin.Log.LogWarning((object)$"Scry could not group entries, and lists them ungrouped: {arg5}");
			}
			while (grouping != null)
			{
				started = CatalogTiming.Start();
				bool flag2 = false;
				try
				{
					flag2 = grouping.MoveNext();
				}
				catch (Exception arg6)
				{
					Plugin.Log.LogWarning((object)$"Scry could not group all entries, and lists some ungrouped: {arg6}");
				}
				CatalogTiming.Add("grouping", started);
				if (!flag2)
				{
					break;
				}
				yield return $"Grouping entries: {grouping.Current:N0}";
			}
			job.Entries = entries;
			Plugin.Note($"Scry's catalog, by part (ms): {CatalogTiming.Report()}; {GC.CollectionCount(0) - collections} garbage collections meanwhile.");
			Faults.TellSkipped();
		}

		private static void ReadPrefab(string name, Found found, Dictionary<string, Found> effects, List<Component> components, List<Leftover> leftovers)
		{
			components.Clear();
			long started = CatalogTiming.Start();
			Describe(found, name, Origins.Prefabs.Of(name), effects, components);
			CatalogTiming.Add("describe", started);
			if (components.Count != 0)
			{
				Knowledge.Read(found.Prefab, components);
				started = CatalogTiming.Start();
				Relations.Read(found.Prefab, components);
				CatalogTiming.Add("links", started);
				started = CatalogTiming.Start();
				FindLeftovers(found.Prefab, leftovers);
				CatalogTiming.Add("leftovers", started);
			}
		}

		private static void GatherHelper(Relations.Helper helper, Dictionary<string, Found> registered, Dictionary<string, Found> effects, List<Component> components)
		{
			GameObject prefab = helper.Prefab;
			if ((Object)(object)prefab == (Object)null || registered.ContainsKey(((Object)prefab).name) || effects.ContainsKey(((Object)prefab).name))
			{
				return;
			}
			string user = helper.User;
			if (user.StartsWith("se:", StringComparison.Ordinal))
			{
				return;
			}
			components.Clear();
			try
			{
				prefab.GetComponentsInChildren<Component>(true, components);
			}
			catch (Exception ex)
			{
				Faults.Skip("effects of what prefabs spawn", ((Object)prefab).name, ex);
				return;
			}
			Origin ownerOrigin = Origins.Prefabs.Of(user);
			foreach (Component component in components)
			{
				if (!((Object)(object)component == (Object)null))
				{
					try
					{
						Gather(component, user, ownerOrigin, effects, user, user, helper.Part);
					}
					catch (Exception ex2)
					{
						Faults.Skip("effects of what prefabs spawn", ((Object)prefab).name, ex2);
					}
				}
			}
		}

		private static void MakeEntry(List<Entry> entries, string name, Found found, Dictionary<string, Found> effects, bool registeredOrigin)
		{
			long started = CatalogTiming.Start();
			try
			{
				entries.Add(ToEntry(name, found, effects, registeredOrigin));
			}
			catch (Exception ex)
			{
				Failed("entry", name, ex);
			}
			CatalogTiming.Add("entries", started);
		}

		public static void Forget()
		{
			_recipes = null;
			FailedKinds.Clear();
		}

		private static void Failed(string what, string name, Exception ex)
		{
			if (Trouble.IsGameChange(ex))
			{
				Faults.Skip(what + " entries", name, ex);
			}
			else if (FailedKinds.Add(what + "|" + ex.GetType().Name + "|" + ex.Message))
			{
				Plugin.Log.LogWarning((object)$"Scry leaves the {what} {name} out of its catalog (said once for this kind of failure): {ex}");
			}
		}

		private static Entry ToEntry(string name, Found found, Dictionary<string, Found> effects, bool registeredOrigin)
		{
			effects.TryGetValue(name, out var value);
			if (value != null)
			{
				found.Traits.FromEffectList = true;
			}
			Entry entry = new Entry
			{
				Name = name,
				DisplayName = Localize(found.Token),
				Kind = Kinds.Of(found.Traits),
				Group = Kinds.GroupLabel(Kinds.GroupOf(found.Traits)),
				GroupOrder = (int)Kinds.GroupOf(found.Traits),
				PlayedIn = ((value != null) ? value.Fields.ToArray() : new string[0]),
				Empty = Kinds.IsEmpty(found.Traits),
				ExtraLevels = found.ExtraLevels,
				HasWear = found.HasWear,
				Source = found.Prefab,
				Icon = found.Icon,
				Origin = (registeredOrigin ? Origins.Prefabs.Of(name) : Provenance.Combine(found.UserOrigins)),
				Registered = registeredOrigin,
				Components = found.Components.ToArray(),
				Biomes = Knowledge.Biomes(name),
				ModName = Knowledge.ModName(name)
			};
			if (entry.Kind == Kind.Other)
			{
				Group obj = Groups.Role(found.Traits);
				entry.Group = obj.Name;
				entry.GroupOrder = obj.Order;
			}
			if ((Object)(object)found.Prefab != (Object)null)
			{
				GameObject prefab = found.Prefab;
				entry.LooksFrom(delegate
				{
					try
					{
						int defaultLook;
						return (Names: Variants.Of(prefab, out defaultLook), Default: defaultLook);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Scry could not work out the looks of " + name + ", and offers none: " + ex.Message));
						throw;
					}
				});
				entry.Stations = StationsOf(found.Prefab);
			}
			if (value != null)
			{
				entry.UsedBy.AddRange(value.Users);
				entry.UsedBy.Sort(StringComparer.OrdinalIgnoreCase);
			}
			return entry;
		}

		private static StationUse[] StationsOf(GameObject prefab)
		{
			List<StationUse> list = new List<StationUse>();
			if ((Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null && _recipes != null && _recipes.TryGetValue(((Object)prefab).name, out var value))
			{
				foreach (Recipe item in value)
				{
					list.Add(((Object)(object)item.m_craftingStation != (Object)null) ? new StationUse(((Object)((Component)item.m_craftingStation).gameObject).name, Localize(item.m_craftingStation.m_name), Math.Max(1, item.m_minStationLevel)) : new StationUse("hand", "By hand", 1));
				}
			}
			Piece component = prefab.GetComponent<Piece>();
			if ((Object)(object)component != (Object)null && (Object)(object)component.m_craftingStation != (Object)null)
			{
				list.Add(new StationUse(((Object)((Component)component.m_craftingStation).gameObject).name, Localize(component.m_craftingStation.m_name), 1));
			}
			foreach (Making making in Knowledge.MadeOf(((Object)prefab).name))
			{
				if (!list.Exists((StationUse u) => u.Name == making.Station))
				{
					GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(making.Station) : null);
					string shown = (((Object)(object)val != (Object)null) ? Localize(val.GetComponent<Piece>()?.m_name) : "");
					list.Add(new StationUse(making.Station, shown, 1));
				}
			}
			return list.ToArray();
		}

		private static void IndexRecipes()
		{
			_recipes = new Dictionary<string, List<Recipe>>(StringComparer.Ordinal);
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			foreach (Recipe recipe in instance.m_recipes)
			{
				if (!((Object)(object)recipe == (Object)null) && recipe.m_enabled && !((Object)(object)recipe.m_item == (Object)null))
				{
					string name = ((Object)((Component)recipe.m_item).gameObject).name;
					if (!_recipes.TryGetValue(name, out var value))
					{
						value = (_recipes[name] = new List<Recipe>());
					}
					value.Add(recipe);
				}
			}
		}

		private static void Describe(Found found, string owner, Origin ownerOrigin, Dictionary<string, Found> effects, List<Component> components)
		{
			PrefabTraits traits = (found.Traits = new PrefabTraits());
			try
			{
				found.Prefab.GetComponentsInChildren<Component>(true, components);
			}
			catch (Exception ex)
			{
				components.Clear();
				Faults.Skip("effect lists", owner, ex);
				return;
			}
			foreach (Component component in components)
			{
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				try
				{
					Note(component, found, traits);
					found.Components.Add(((object)component).GetType().Name);
					Gather(component, owner, ownerOrigin, effects, owner, owner);
					if (component is Humanoid)
					{
						foreach (GameObject item in Relations.CarriedItems(found.Prefab))
						{
							SharedData val = item.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
							if (val != null)
							{
								string text = AttackName(item);
								Gather(val, owner, ownerOrigin, effects, owner, owner, text);
								if (val.m_attack != null)
								{
									Gather(val.m_attack, owner, ownerOrigin, effects, owner, owner, text);
								}
								if (val.m_secondaryAttack != null)
								{
									Gather(val.m_secondaryAttack, owner, ownerOrigin, effects, owner, owner, text + " (second)");
								}
							}
						}
					}
					ItemDrop val2 = (ItemDrop)(object)((component is ItemDrop) ? component : null);
					if (val2 != null && val2.m_itemData?.m_shared != null)
					{
						SharedData shared = val2.m_itemData.m_shared;
						Gather(shared, owner, ownerOrigin, effects, owner, owner);
						if (shared.m_attack != null)
						{
							Gather(shared.m_attack, owner, ownerOrigin, effects, owner, owner, "Attack");
						}
						if (shared.m_secondaryAttack != null)
						{
							Gather(shared.m_secondaryAttack, owner, ownerOrigin, effects, owner, owner, "Second attack");
						}
					}
				}
				catch (Exception ex2)
				{
					Faults.Skip("effect lists", owner, ex2);
				}
			}
		}

		private static void Note(Component component, Found found, PrefabTraits traits)
		{
			if (component is Interactable)
			{
				traits.IsUsable = true;
			}
			if (!(component is OfferingBowl))
			{
				if (!(component is Ragdoll))
				{
					if (!(component is Container))
					{
						Character val = (Character)(object)((component is Character) ? component : null);
						if (val == null)
						{
							ItemDrop val2 = (ItemDrop)(object)((component is ItemDrop) ? component : null);
							if (val2 == null)
							{
								Piece val3 = (Piece)(object)((component is Piece) ? component : null);
								if (val3 == null)
								{
									if (!(component is Projectile))
									{
										if (!(component is ParticleSystem))
										{
											if (component is ParticleSystemRenderer)
											{
												return;
											}
											if (!(component is Renderer))
											{
												if (!(component is Light))
												{
													if (!(component is AudioSource) && !(component is ZSFX))
													{
														Collider val4 = (Collider)(object)((component is Collider) ? component : null);
														if (val4 == null)
														{
															if (!(component is TreeBase))
															{
																if (!(component is TreeLog))
																{
																	if (!(component is MineRock) && !(component is MineRock5))
																	{
																		if (!(component is Pickable) && !(component is PickableItem))
																		{
																			if (!(component is Plant))
																			{
																				Destructible val5 = (Destructible)(object)((component is Destructible) ? component : null);
																				if (val5 == null)
																				{
																					DropOnDestroyed val6 = (DropOnDestroyed)(object)((component is DropOnDestroyed) ? component : null);
																					if (val6 == null)
																					{
																						if (!(component is SpawnArea) && !(component is CreatureSpawner))
																						{
																							LevelEffects val7 = (LevelEffects)(object)((component is LevelEffects) ? component : null);
																							if (val7 == null)
																							{
																								WearNTear val8 = (WearNTear)(object)((component is WearNTear) ? component : null);
																								if (val8 != null)
																								{
																									found.HasWear |= ((Object)(object)val8.m_worn != (Object)null && (Object)(object)val8.m_worn != (Object)(object)val8.m_new) || ((Object)(object)val8.m_broken != (Object)null && (Object)(object)val8.m_broken != (Object)(object)val8.m_new);
																								}
																							}
																							else
																							{
																								found.ExtraLevels = Math.Max(found.ExtraLevels, val7.m_levelSetups?.Count ?? 0);
																							}
																						}
																						else
																						{
																							traits.HasSpawner = true;
																						}
																					}
																					else if (val6.m_dropWhenDestroyed?.m_drops != null && val6.m_dropWhenDestroyed.m_drops.Count > 0)
																					{
																						traits.HasDrops = true;
																					}
																				}
																				else
																				{
																					traits.HasDestructible = true;
																					if ((Object)(object)Knowledge.MinedInside(val5.m_spawnWhenDestroyed) != (Object)null)
																					{
																						traits.BreaksIntoResource = true;
																					}
																				}
																			}
																			else
																			{
																				traits.HasResource = true;
																				traits.HasPlant = true;
																			}
																		}
																		else
																		{
																			traits.HasResource = true;
																			traits.IsPicked = true;
																		}
																	}
																	else
																	{
																		traits.HasResource = true;
																		traits.IsMined = true;
																	}
																}
																else
																{
																	traits.HasResource = true;
																	traits.IsLog = true;
																}
															}
															else
															{
																traits.HasResource = true;
																traits.IsTree = true;
															}
														}
														else if (!val4.isTrigger)
														{
															traits.HasSolidCollider = true;
														}
													}
													else
													{
														traits.HasAudio = true;
													}
												}
												else
												{
													traits.HasLight = true;
												}
											}
											else
											{
												traits.HasRenderer = true;
											}
										}
										else
										{
											traits.HasParticles = true;
										}
									}
									else
									{
										traits.HasProjectile = true;
									}
								}
								else
								{
									if (((Behaviour)val3).enabled)
									{
										traits.HasPiece = true;
									}
									found.Token = found.Token ?? val3.m_name;
									found.Icon = found.Icon ?? val3.m_icon;
								}
								return;
							}
							traits.HasItemDrop = true;
							SharedData val9 = val2.m_itemData?.m_shared;
							if (val9 != null)
							{
								found.Token = found.Token ?? val9.m_name;
								if (found.Icon == null && val9.m_icons != null && val9.m_icons.Length != 0)
								{
									found.Icon = val9.m_icons[0];
								}
							}
						}
						else
						{
							traits.HasCharacter = true;
							found.Token = found.Token ?? val.m_name;
						}
					}
					else
					{
						traits.HasContainer = true;
					}
				}
				else
				{
					traits.HasRagdoll = true;
				}
			}
			else
			{
				traits.IsAltar = true;
			}
		}

		internal static string GameName(GameObject item)
		{
			string text = ((!((Object)(object)item != (Object)null)) ? null : item.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_name);
			if (string.IsNullOrEmpty(text) || !text.StartsWith("$", StringComparison.Ordinal))
			{
				return null;
			}
			string text2 = Localize(text);
			if (text2.Length <= 0 || text2.StartsWith("$", StringComparison.Ordinal))
			{
				return null;
			}
			return text2;
		}

		internal static string AttackName(GameObject item)
		{
			SharedData val = item.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
			string text = ((val != null) ? Localize(val.m_name) : "");
			if (text.Length <= 0 || text.StartsWith("$", StringComparison.Ordinal))
			{
				return ((Object)item).name;
			}
			return text;
		}

		private static void FindLeftovers(GameObject prefab, List<Leftover> found)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			try
			{
				Character component = prefab.GetComponent<Character>();
				if ((Object)(object)component != (Object)null)
				{
					Thrown(component.m_deathEffects, prefab, "remains", ragdolls: true);
				}
				TreeBase component2 = prefab.GetComponent<TreeBase>();
				if ((Object)(object)component2 != (Object)null)
				{
					Add(component2.m_logPrefab, prefab, "log");
					Add(component2.m_stubPrefab, prefab, "stump");
					Thrown(component2.m_destroyedEffect, prefab, "debris", ragdolls: false);
				}
				TreeLog component3 = prefab.GetComponent<TreeLog>();
				if ((Object)(object)component3 != (Object)null)
				{
					Add(component3.m_subLogPrefab, prefab, "half");
					Thrown(component3.m_destroyedEffect, prefab, "debris", ragdolls: false);
				}
				Destructible component4 = prefab.GetComponent<Destructible>();
				if ((Object)(object)component4 != (Object)null)
				{
					Add(component4.m_spawnWhenDestroyed, prefab, "broken");
					Thrown(component4.m_destroyedEffect, prefab, "debris", ragdolls: false);
				}
				Thrown(prefab.GetComponent<WearNTear>()?.m_destroyedEffect, prefab, "debris", ragdolls: false);
				Thrown(prefab.GetComponent<MineRock>()?.m_destroyedEffect, prefab, "debris", ragdolls: false);
				Thrown(prefab.GetComponent<MineRock5>()?.m_destroyedEffect, prefab, "debris", ragdolls: false);
			}
			catch (Exception ex)
			{
				Faults.Skip("what things leave behind", ((Object)prefab).name, ex);
			}
			void Add(GameObject thing, GameObject owner, string role)
			{
				if ((Object)(object)thing != (Object)null && (Object)(object)owner != (Object)null)
				{
					found.Add(new Leftover(((Object)thing).name, ((Object)owner).name, role));
				}
			}
			void Thrown(EffectList list, GameObject owner, string role, bool ragdolls)
			{
				if (list?.m_effectPrefabs != null)
				{
					EffectData[] effectPrefabs = list.m_effectPrefabs;
					for (int i = 0; i < effectPrefabs.Length; i++)
					{
						GameObject val = effectPrefabs[i]?.m_prefab;
						if (!((Object)(object)val == (Object)null))
						{
							if (ragdolls && (Object)(object)val.GetComponent<Ragdoll>() != (Object)null)
							{
								Add(val, owner, "ragdoll");
							}
							else if (Ghost.IsDebris(val))
							{
								Add(val, owner, role);
							}
						}
					}
				}
			}
		}

		public static string Localize(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return "";
			}
			if (Localized.TryGetValue(token, out var value))
			{
				return value;
			}
			string text;
			try
			{
				text = Naming.Plain((Localization.instance != null) ? Localization.instance.Localize(token) : token).Trim();
				if (string.IsNullOrEmpty(text) || text.StartsWith("[", StringComparison.Ordinal) || text.StartsWith("$", StringComparison.Ordinal))
				{
					text = "";
				}
			}
			catch
			{
				text = "";
			}
			if (Localized.Count > 20000)
			{
				Localized.Clear();
			}
			Localized[token] = text;
			return text;
		}

		private static void GatherInterface(Dictionary<string, Found> effects)
		{
			HashSet<Transform> hashSet = new HashSet<Transform>();
			AddRoot((Component)(object)InventoryGui.instance, hashSet);
			AddRoot((Component)(object)Hud.instance, hashSet);
			AddRoot((Component)(object)StoreGui.instance, hashSet);
			AddRoot((Component)(object)Minimap.instance, hashSet);
			AddRoot((Component)(object)MessageHud.instance, hashSet);
			AddRoot((Component)(object)Menu.instance, hashSet);
			AddRoot((Component)(object)Chat.instance, hashSet);
			foreach (Transform item in hashSet)
			{
				Component[] componentsInChildren = ((Component)item).GetComponentsInChildren<Component>(true);
				foreach (Component val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null))
					{
						try
						{
							Gather(val, "(interface)", Origin.Vanilla, effects, null, "(interface)");
						}
						catch (Exception ex)
						{
							Faults.Skip("interface sounds", "(interface)", ex);
						}
					}
				}
			}
		}

		private static void AddRoot(Component part, HashSet<Transform> roots)
		{
			if ((Object)(object)part != (Object)null)
			{
				roots.Add(part.transform.root);
			}
		}

		private static void Gather(object owner, string ownerName, Origin ownerOrigin, Dictionary<string, Found> effects, string ownerKey, string shown, string part = null)
		{
			foreach (ListOn item in ListsOn(owner))
			{
				EffectList list = item.List;
				if (list.m_effectPrefabs == null || list.m_effectPrefabs.Length == 0)
				{
					continue;
				}
				string text = item.Label;
				if (part != null)
				{
					text = part + ": " + text.ToLowerInvariant();
				}
				EffectLinks.Note(list, shown, ownerKey, text);
				EffectData[] effectPrefabs = list.m_effectPrefabs;
				for (int i = 0; i < effectPrefabs.Length; i++)
				{
					GameObject val = effectPrefabs[i]?.m_prefab;
					if (!((Object)(object)val == (Object)null))
					{
						if (!effects.TryGetValue(((Object)val).name, out var value))
						{
							value = new Found
							{
								Prefab = val
							};
							effects[((Object)val).name] = value;
						}
						if (value.Users.Add(ownerName))
						{
							value.UserOrigins.Add(ownerOrigin);
						}
						value.Fields.Add(((ownerName == "(interface)") ? "ui:" : (ownerName.StartsWith("status effect ", StringComparison.Ordinal) ? "se:" : "")) + item.Field);
					}
				}
			}
		}

		internal static IEnumerable<ListOn> ListsOn(object owner)
		{
			(FieldInfo Field, string Name, string Label)[] array = OwnLists(owner.GetType());
			for (int i = 0; i < array.Length; i++)
			{
				(FieldInfo Field, string Name, string Label) tuple = array[i];
				FieldInfo item = tuple.Field;
				string item2 = tuple.Name;
				string item3 = tuple.Label;
				object? value = item.GetValue(owner);
				EffectList val = (EffectList)((value is EffectList) ? value : null);
				if (val != null)
				{
					yield return new ListOn
					{
						Field = item2,
						Label = item3,
						List = val
					};
				}
			}
			(FieldInfo Outer, (FieldInfo Field, string Name, string Label)[] Inner)[] array2 = NestedLists(owner.GetType());
			for (int i = 0; i < array2.Length; i++)
			{
				(FieldInfo, (FieldInfo, string, string)[]) tuple2 = array2[i];
				FieldInfo outer = tuple2.Item1;
				array = tuple2.Item2;
				object value2 = outer.GetValue(owner);
				if (value2 == null)
				{
					continue;
				}
				(FieldInfo Field, string Name, string Label)[] array3;
				if (value2 is IEnumerable enumerable)
				{
					foreach (object item4 in enumerable)
					{
						if (item4 == null)
						{
							continue;
						}
						string of = WhatFor(item4);
						array3 = array;
						for (int j = 0; j < array3.Length; j++)
						{
							(FieldInfo Field, string Name, string Label) tuple3 = array3[j];
							FieldInfo item5 = tuple3.Field;
							string item6 = tuple3.Name;
							string item7 = tuple3.Label;
							object? value3 = item5.GetValue(item4);
							EffectList val2 = (EffectList)((value3 is EffectList) ? value3 : null);
							if (val2 != null)
							{
								yield return new ListOn
								{
									Field = item6,
									Label = ((of.Length > 0) ? Naming.NestedListLabel(outer.Name, item5.Name, of) : item7),
									List = val2
								};
							}
						}
					}
					continue;
				}
				array3 = array;
				for (int j = 0; j < array3.Length; j++)
				{
					(FieldInfo Field, string Name, string Label) tuple4 = array3[j];
					FieldInfo item8 = tuple4.Field;
					string item9 = tuple4.Name;
					string item10 = tuple4.Label;
					object? value4 = item8.GetValue(value2);
					EffectList val3 = (EffectList)((value4 is EffectList) ? value4 : null);
					if (val3 != null)
					{
						yield return new ListOn
						{
							Field = item9,
							Label = item10,
							List = val3
						};
					}
				}
			}
		}

		private static string WhatFor(object item)
		{
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			Type type = item.GetType();
			if (!NamingFields.TryGetValue(type, out var value))
			{
				value = null;
				try
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (!fieldInfo.IsNotSerialized && (fieldInfo.IsPublic || ((MemberInfo)fieldInfo).GetCustomAttribute<SerializeField>() != null) && (fieldInfo.FieldType.IsEnum || typeof(Object).IsAssignableFrom(fieldInfo.FieldType)))
						{
							value = fieldInfo;
							break;
						}
					}
				}
				catch (Exception ex)
				{
					Faults.Skip("reading of a type's fields", type.Name, ex);
				}
				NamingFields[type] = value;
			}
			if (value == null)
			{
				return "";
			}
			object value2 = value.GetValue(item);
			ItemDrop val = (ItemDrop)((value2 is ItemDrop) ? value2 : null);
			Component val2;
			Object val3;
			if (val == null)
			{
				val2 = (Component)((value2 is Component) ? value2 : null);
				if (val2 == null)
				{
					val3 = (Object)((value2 is Object) ? value2 : null);
					if (val3 != null)
					{
						goto IL_019c;
					}
					if (!(value2 is Enum obj))
					{
						goto IL_0203;
					}
					if (Convert.ToInt64(obj) == 0L)
					{
						return "";
					}
					return string.Join(", ", obj.ToString().Split(new string[1] { ", " }, StringSplitOptions.RemoveEmptyEntries).Select(Naming.FieldLabel));
				}
			}
			else
			{
				if ((Object)(object)val != (Object)null)
				{
					string text = Naming.Plain(Localize(val.m_itemData?.m_shared?.m_name ?? ""));
					if (text.Length <= 0 || text.StartsWith("$", StringComparison.Ordinal))
					{
						return ((Object)((Component)val).gameObject).name;
					}
					return text;
				}
				val2 = (Component)value2;
			}
			if (!((Object)(object)val2 != (Object)null))
			{
				val3 = (Object)value2;
				goto IL_019c;
			}
			return ((Object)val2.gameObject).name;
			IL_0203:
			return "";
			IL_019c:
			if (val3 != (Object)null)
			{
				return val3.name;
			}
			goto IL_0203;
		}

		private static (FieldInfo Field, string Name, string Label)[] OwnLists(Type type)
		{
			if (OwnListsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			value = (from f in EffectFields(type)
				select (f: f, Name: f.Name, Naming.EffectListLabel(f.Name))).ToArray();
			OwnListsByType[type] = value;
			return value;
		}

		private static (FieldInfo Outer, (FieldInfo Field, string Name, string Label)[] Inner)[] NestedLists(Type type)
		{
			if (NestedListsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			List<(FieldInfo, (FieldInfo, string, string)[])> list = new List<(FieldInfo, (FieldInfo, string, string)[])>();
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object) && type2 != typeof(MonoBehaviour) && type2 != typeof(Component) && type2 != typeof(ScriptableObject))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo outer in fields)
					{
						if (outer.IsNotSerialized || (!outer.IsPublic && ((MemberInfo)outer).GetCustomAttribute<SerializeField>() == null))
						{
							continue;
						}
						Type fieldType = outer.FieldType;
						Type type3 = (fieldType.IsArray ? fieldType.GetElementType() : ((fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>)) ? fieldType.GetGenericArguments()[0] : fieldType));
						if (HoldsLists(type3))
						{
							(FieldInfo, string, string)[] array = (from f in EffectFields(type3)
								select (f: f, outer.Name + "." + f.Name, Naming.NestedListLabel(outer.Name, f.Name))).ToArray();
							if (array.Length != 0)
							{
								list.Add((outer, array));
							}
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception ex)
			{
				Faults.Skip("reading of a type's fields", type.Name, ex);
				list.Clear();
			}
			value = list.ToArray();
			NestedListsByType[type] = value;
			return value;
		}

		private static bool HoldsLists(Type type)
		{
			if (type == null || type.IsPrimitive || type.IsEnum || type == typeof(string) || !type.IsSerializable || type.Namespace != null)
			{
				return false;
			}
			if (typeof(Object).IsAssignableFrom(type))
			{
				return false;
			}
			if (type != typeof(EffectList) && type != typeof(DropTable) && type != typeof(Attack) && type != typeof(ItemData))
			{
				return type != typeof(SharedData);
			}
			return false;
		}

		internal static FieldInfo[] EffectFields(Type type)
		{
			if (EffectFieldsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (fieldInfo.FieldType == typeof(EffectList))
						{
							list.Add(fieldInfo);
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception ex)
			{
				Faults.Skip("reading of a type's fields", type.Name, ex);
				list.Clear();
			}
			value = list.ToArray();
			EffectFieldsByType[type] = value;
			return value;
		}
	}
	internal sealed class CatalogJob
	{
		private readonly ZNetScene _scene = ZNetScene.instance;

		private readonly IEnumerator<string> _steps;

		private readonly FrameShare _share = new FrameShare(4.0);

		private readonly Stopwatch _all = new Stopwatch();

		private const double LongPieceMs = 8.0;

		private readonly List<(string What, double Ms)> _long = new List<(string, double)>();

		internal string Piece;

		public List<Entry> Entries { get; internal set; }

		public ZNetScene Scene => _scene;

		public string Failure { get; private set; }

		public string Progress { get; private set; } = "Reading the catalog";

		public bool Done
		{
			get
			{
				if (Entries == null)
				{
					return Failure != null;
				}
				return true;
			}
		}

		public double WorkMs { get; private set; }

		public int Frames { get; private set; }

		public double ElapsedMs => _all.Elapsed.TotalMilliseconds;

		public CatalogJob()
		{
			_steps = CatalogBuilder.Steps(this);
		}

		public bool Advance(double budgetMs)
		{
			if (Done)
			{
				return true;
			}
			if (ZNetScene.instance != _scene)
			{
				Failure = "the world was left";
				return true;
			}
			_all.Start();
			Stopwatch stopwatch = Stopwatch.StartNew();
			int num = 0;
			try
			{
				while (_share.MayBegin(stopwatch.Elapsed.TotalMilliseconds, num, budgetMs))
				{
					double totalMilliseconds = stopwatch.Elapsed.TotalMilliseconds;
					string progress = Progress;
					Piece = null;
					bool num2 = _steps.MoveNext();
					double num3 = stopwatch.Elapsed.TotalMilliseconds - totalMilliseconds;
					_share.Took(num3);
					if (num3 > 8.0 && Plugin.LogPreviews)
					{
						_long.Add(((Piece != null) ? ("the prefab " + Piece) : progress, num3));
					}
					num++;
					if (!num2)
					{
						if (Entries == null)
						{
							Failure = "it ended without a catalog";
						}
						break;
					}
					Progress = _steps.Current;
				}
			}
			catch (Exception ex)
			{
				Failure = ex.ToString();
			}
			WorkMs += stopwatch.Elapsed.TotalMilliseconds;
			Frames++;
			if (Done)
			{
				_all.Stop();
				_steps.Dispose();
				if (_long.Count > 0)
				{
					Plugin.Note(string.Format("Scry's catalog pieces over {0:0} ms ({1}): {2}.", 8.0, _long.Count, string.Join(", ", from p in _long.OrderByDescending(((string What, double Ms) p) => p.Ms).Take(25)
						select $"{p.What} {p.Ms:0}")));
				}
			}
			return Done;
		}
	}
	internal static class CatalogTiming
	{
		private static readonly Dictionary<string, double> Parts = new Dictionary<string, double>();

		public static long Start()
		{
			return Stopwatch.GetTimestamp();
		}

		public static void Add(string part, long started)
		{
			double num = (double)(Stopwatch.GetTimestamp() - started) * 1000.0 / (double)Stopwatch.Frequency;
			Parts.TryGetValue(part, out var value);
			Parts[part] = value + num;
		}

		public static void Reset()
		{
			Parts.Clear();
		}

		public static string Report()
		{
			return string.Join(", ", from p in Parts
				orderby p.Value descending
				select $"{p.Key} {p.Value:0}");
		}
	}
	internal static class EffectLinks
	{
		private static readonly Dictionary<string, List<EffectUse>> Uses;

		private static readonly HashSet<(EffectList, string)> Noted;

		private static readonly Dictionary<string, List<PlaysInRow>> Rows;

		static EffectLinks()
		{
			Uses = new Dictionary<string, List<EffectUse>>(StringComparer.Ordinal);
			Noted = new HashSet<(EffectList, string)>();
			Rows = new Dictionary<string, List<PlaysInRow>>(StringComparer.Ordinal);
			WorldCaches.Register("EffectLinks", Clear);
		}

		public static void Clear()
		{
			Uses.Clear();
			Noted.Clear();
			Rows.Clear();
		}

		public static void Note(EffectList list, string owner, string ownerKey, string label)
		{
			if (list?.m_effectPrefabs == null || !Noted.Add((list, owner)))
			{
				return;
			}
			List<string> list2 = new List<string>();
			EffectData[] effectPrefabs = list.m_effectPrefabs;
			foreach (EffectData val in effectPrefabs)
			{
				if (val != null && val.m_enabled && (Object)(object)val.m_prefab != (Object)null)
				{
					list2.Add(((Object)val.m_prefab).name);
				}
			}
			if (list2.Count == 0)
			{
				return;
			}
			EffectUse effectUse = new EffectUse(owner, ownerKey, label, list2, list);
			string[] members = effectUse.Members;
			foreach (string key in members)
			{
				if (!Uses.TryGetValue(key, out var value))
				{
					value = new List<EffectUse>();
					Uses[key] = value;
				}
				value.Add(effectUse);
			}
		}

		public static List<PlaysInRow> For(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return new List<PlaysInRow>();
			}
			if (Rows.TryGetValue(prefabName, out var value))
			{
				return value;
			}
			value = (Uses.TryGetValue(prefabName, out var value2) ? PlaysIn.Rows(value2) : new List<PlaysInRow>());
			Rows[prefabName] = value;
			return value;
		}
	}
	internal static class Grouping
	{
		private static readonly HashSet<string> Told;

		private static readonly HashSet<string> UsedByLinks;

		private static Dictionary<PieceCategory, string> _categoryNames;

		static Grouping()
		{
			Told = new HashSet<string>();
			UsedByLinks = new HashSet<string>(StringComparer.Ordinal) { "Footstep of", "Played by an animation of", "Spawned by", "Carried by", "Shot from" };
			WorldCaches.Register("Grouping", Forget);
		}

		public static IEnumerable<int> Apply(List<Entry> entries, int slice)
		{
			Dictionary<string, Entry> byName = new Dictionary<string, Entry>(StringComparer.Ordinal);
			foreach (Entry entry2 in entries)
			{
				if (entry2.Kind != Kind.StatusEffect && !byName.ContainsKey(entry2.Name))
				{
					byName[entry2.Name] = entry2;
				}
			}
			HashSet<string> weather = new HashSet<string>(StringComparer.Ordinal);
			try
			{
				Weather(weather);
			}
			catch (Exception ex)
			{
				Tell("the weather", ex);
			}
			Dictionary<string, Group> menus;
			try
			{
				menus = Menus();
			}
			catch (Exception ex2)
			{
				Tell("the build menus", ex2);
				menus = new Dictionary<string, Group>();
			}
			for (int i = 0; i < entries.Count; i++)
			{
				Entry entry = entries[i];
				try
				{
					Group? obj = Of(entry, byName, menus, weather);
					if (obj.HasValue)
					{
						entry.Group = obj.Value.Name;
						entry.GroupOrder = obj.Value.Order;
					}
				}
				catch (Exception ex3)
				{
					Tell("an entry", ex3);
				}
				if ((i + 1) % slice == 0)
				{
					yield return i + 1;
				}
			}
			try
			{
				Groups.FollowSpawners(entries, "Spawned by", Groups.Projectile(new Shooter[0]).Name);
			}
			catch (Exception ex4)
			{
				Tell("projectiles spawned by others", ex4);
			}
			try
			{
				Leftovers.JoinOwnersGroups(entries);
			}
			catch (Exception ex5)
			{
				Tell("what is left behind", ex5);
			}
			if (Plugin.LogPreviews)
			{
				Report(entries);
			}
		}

		private static void Report(List<Entry> entries)
		{
			foreach (IGrouping<Kind, Entry> item in from e in entries
				where e.Group.Length > 0
				group e by e.Kind)
			{
				IEnumerable<string> values = from e in item
					group e by e.Group into g
					orderby g.First().GroupOrder
					select $"{g.Key} {g.Count()}";
				Plugin.Note("Scry groups its " + Kinds.Label(item.Key).ToLowerInvariant() + ": " + string.Join(", ", values) + ".");
			}
			List<string> list = (from e in entries
				where (e.Kind == Kind.Effect || e.Kind == Kind.Sound) && e.GroupOrder == Groups.Purpose(new string[0], footstep: false, animation: false).Order
				select e.Name).OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToList();
			List<string> list2 = (from e in entries
				where e.Kind == Kind.Projectile && e.GroupOrder == Groups.Projectile(new Shooter[0]).Order
				select e.Name + ((e.Links.Count > 0) ? (" (" + string.Join("/", e.Links.Select((Link l) => l.Group).Distinct()) + ")") : "")).OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToList();
			if (list2.Count > 0)
			{
				Plugin.Note(string.Format("Scry found nothing that fires {0} projectiles, among them: {1}.", list2.Count, string.Join(", ", list2.Take(100))));
			}
			if (list.Count > 0)
			{
				Plugin.Note(string.Format("Scry found nothing that plays {0} effects and sounds, among them: {1}.", list.Count, string.Join(", ", list.Take(150))));
			}
		}

		private static Group Item(Entry entry, GameObject prefab)
		{
			ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
			SharedData val2 = ((!((Object)(object)val != (Object)null)) ? null : val.m_itemData?.m_shared);
			bool carriedByCreature = entry.Links.Any((Link l) => l.Group == "Carried by");
			bool obtainable = entry.Stations.Length != 0 || Knowledge.SourceLines(entry.Name).Count > 0 || entry.FoundIn.Length != 0 || Knowledge.IsPlacedByWorld(entry.Name) || entry.Links.Any((Link l) => l.Group == "Spawned by");
			return Groups.Item(((object)Unsafe.As<ItemType, ItemType>(ref val2?.m_itemType)/*cast due to .constrained prefix*/).ToString(), ((object)Unsafe.As<SkillType, SkillType>(ref val2?.m_skillType)/*cast due to .constrained prefix*/).ToString(), carriedByCreature, obtainable);
		}

		public static int FoundInLocations(IEnumerable<Entry> entries)
		{
			int order = Groups.CarriedByCreatures.Order;
			int num = 0;
			foreach (Entry entry in entries)
			{
				if (entry.Kind != Kind.Item || entry.GroupOrder != order || entry.FoundIn.Length == 0)
				{
					continue;
				}
				try
				{
					object source = entry.Source;
					Group obj = Item(entry, (GameObject)((source is GameObject) ? source : null));
					if (obj.Order != order)
					{
						entry.Group = obj.Name;
						entry.GroupOrder = obj.Order;
						num++;
					}
				}
				catch (Exception ex)
				{
					Tell("an entry", ex);
				}
			}
			return num;
		}

		private static Group? Of(Entry entry, Dictionary<string, Entry> byName, Dictionary<string, Group> menus, HashSet<string> weather)
		{
			object source = entry.Source;
			GameObject val = (GameObject)((source is GameObject) ? source : null);
			switch (entry.Kind)
			{
			case Kind.Item:
				return Item(entry, val);
			case Kind.Creature:
			{
				Character val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Character>() : null);
				return ((Object)(object)val2 != (Object)null) ? Groups.Creature(((object)Unsafe.As<Faction, Faction>(ref val2.m_faction)/*cast due to .constrained prefix*/).ToString(), val2.m_boss) : Groups.Creature(null, boss: false);
			}
			case Kind.Piece:
			{
				Group value2;
				return menus.TryGetValue(entry.Name, out value2) ? value2 : Groups.InNoMenu;
			}
			case Kind.Effect:
			case Kind.Sound:
			{
				bool footstep = entry.Links.Any((Link l) => l.Group == "Footstep of");
				bool animation = entry.Links.Any((Link l) => l.Group == "Played by an animation of");
				List<string> list = entry.PlayedIn.ToList();
				if (weather.Contains(entry.Name))
				{
					list.Add("weather");
				}
				if (Knowledge.IsPlacedByWorld(entry.Name))
				{
					list.Add("ambience");
				}
				if (entry.Links.Any((Link l) => l.Group == "Spawned by" && l.Target.StartsWith("se:", StringComparison.Ordinal)))
				{
					list.Add("se:spawned");
				}
				if (entry.LeftBy.Count > 0)
				{
					list.Add("left when destroyed");
				}
				Group value = Groups.Purpose(list, footstep, animation);
				if (value.Order == Groups.Purpose(new string[0], footstep: false, animation: false).Order && Users(entry).Any())
				{
					value = Groups.Purpose(new string[1] { "" }, footstep: false, animation: false);
				}
				return value;
			}
			case Kind.Projectile:
				return Groups.Projectile(Shooters(entry, byName));
			case Kind.StatusEffect:
				return Groups.StatusEffect(Givers(entry, byName));
			default:
				return null;
			}
		}

		private static IEnumerable<string> Users(Entry entry)
		{
			foreach (string item in entry.UsedBy)
			{
				yield return item;
			}
			foreach (Link link in entry.Links)
			{
				if (UsedByLinks.Contains(link.Group))
				{
					yield return link.Target;
				}
			}
		}

		private static IEnumerable<Giver> Givers(Entry entry, Dictionary<string, Entry> byName)
		{
			foreach (Link link in entry.Links)
			{
				if (link.Group != "Given by")
				{
					continue;
				}
				Entry value;
				Kind kind = (byName.TryGetValue(link.Target, out value) ? value.Kind : Kind.Other);
				if (link.Notes.Count == 0)
				{
					yield return new Giver(kind, "");
				}
				foreach (string note in link.Notes)
				{
					yield return new Giver(kind, note);
				}
			}
		}

		private static void Weather(HashSet<string> names)
		{
			List<EnvSetup> list = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.m_environments : null);
			if (list == null)
			{
				return;
			}
			foreach (EnvSetup item in list)
			{
				if (item == null)
				{
					continue;
				}
				if ((Object)(object)item.m_envObject != (Object)null)
				{
					names.Add(((Object)item.m_envObject).name);
				}
				if (item.m_psystems == null)
				{
					continue;
				}
				GameObject[] psystems = item.m_psystems;
				foreach (GameObject val in psystems)
				{
					if ((Object)(object)val != (Object)null)
					{
						names.Add(((Object)val).name);
					}
				}
			}
		}

		private static IEnumerable<Shooter> Shooters(Entry entry, Dictionary<string, Entry> byName)
		{
			foreach (Link link in entry.Links)
			{
				if ((!(link.Group != "Shot from") || !(link.Group != "Spawned by")) && byName.TryGetValue(link.Target, out var value))
				{
					object source = value.Source;
					GameObject val = (GameObject)((source is GameObject) ? source : null);
					ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
					SharedData val3 = ((!((Object)(object)val2 != (Object)null)) ? null : val2.m_itemData?.m_shared);
					string skill = ((val3 != null) ? ((object)Unsafe.As<SkillType, SkillType>(ref val3.m_skillType)/*cast due to .constrained prefix*/).ToString() : "");
					string ammoType = ((val3 != null && ((int)val3.m_itemType == 9 || (int)val3.m_itemType == 23)) ? val3.m_ammoType : "");
					bool carriedByCreature = value.Links.Any((Link l) => l.Group == "Carried by") && value.Stations.Length == 0;
					yield return new Shooter(value.Kind, skill, carriedByCreature, ammoType);
				}
			}
		}

		private static Dictionary<string, Group> Menus()
		{
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, Group> dictionary = new Dictionary<string, Group>(StringComparer.Ordinal);
			List<GameObject> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_items : null);
			if (list == null)
			{
				return dictionary;
			}
			List<(string, bool, PieceTable)> list2 = new List<(string, bool, PieceTable)>();
			foreach (GameObject item2 in list)
			{
				SharedData val = ((!((Object)(object)item2 != (Object)null)) ? null : item2.GetComponent<ItemDrop>()?.m_itemData?.m_shared);
				PieceTable val2 = val?.m_buildPieces;
				if (!((Object)(object)val2 == (Object)null) && val2.m_pieces != null)
				{
					bool flag = ((Object)item2).name == "Hammer";
					(string, bool, PieceTable) item = (CatalogBuilder.Localize(val.m_name), flag, val2);
					if (flag)
					{
						list2.Insert(0, item);
					}
					else
					{
						list2.Add(item);
					}
				}
			}
			for (int i = 0; i < list2.Count; i++)
			{
				var (tool, mainTool, val3) = list2[i];
				foreach (Piece item3 in (from p in val3.m_pieces
					where (Object)(object)p != (Object)null
					select p.GetComponent<Piece>() into p
					where (Object)(object)p != (Object)null
					select p).ToList())
				{
					string name = ((Object)((Component)item3).gameObject).name;
					if (!dictionary.ContainsKey(name))
					{
						int num = ((val3.m_categories != null) ? val3.m_categories.IndexOf(item3.m_category) : (-1));
						string text = ((num >= 0 && val3.m_categoryLabels != null && num < val3.m_categoryLabels.Count) ? CatalogBuilder.Localize(val3.m_categoryLabels[num]) : "");
						if (text.Length == 0)
						{
							text = CategoryName(item3.m_category);
						}
						dictionary[name] = Groups.Piece(tool, i, mainTool, text, (num >= 0) ? num : (500 + item3.m_category));
					}
				}
			}
			return dictionary;
		}

		private unsafe static string CategoryName(PieceCategory category)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Invalid comparison between Unknown and I4
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			if (_categoryNames == null)
			{
				_categoryNames = new Dictionary<PieceCategory, string>();
				try
				{
					string[] names = Enum.GetNames(typeof(PieceCategory));
					Array values = Enum.GetValues(typeof(PieceCategory));
					for (int i = 0; i < names.Length && i < values.Length; i++)
					{
						PieceCategory key = (PieceCategory)values.GetValue(i);
						if (!_categoryNames.ContainsKey(key))
						{
							_categoryNames[key] = names[i];
						}
					}
				}
				catch (Exception ex)
				{
					Tell("the build tabs' names", ex);
				}
			}
			if ((int)category == 100)
			{
				return "Every tab";
			}
			string value;
			string text = (_categoryNames.TryGetValue(category, out value) ? value : ((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString());
			if (text.IndexOf(' ') < 0)
			{
				return Naming.FieldLabel(text);
			}
			return text;
		}

		public static void Forget()
		{
			_categoryNames = null;
		}

		private static void Tell(string what, Exception ex)
		{
			if (Told.Add(what + "|" + ex.GetType().Name + "|" + ex.Message))
			{
				Plugin.Log.LogWarning((object)("Scry could not group " + what + ", and lists it ungrouped (said once): " + ex.Message));
			}
		}
	}
	internal struct Source
	{
		public string Text;

		public string Prefab;

		public Source(string text, string prefab)
		{
			Text = text;
			Prefab = prefab;
		}
	}
	internal struct Summon
	{
		public string Boss;

		public string Item;

		public int Count;

		public string Place;

		public string PlacePrefab;

		public static Summon Of(OfferingBowl bowl, string place, string placePrefab)
		{
			return new Summon
			{
				Boss = (((Object)(object)bowl.m_bossPrefab != (Object)null) ? ((Object)bowl.m_bossPrefab).name : null),
				Item = (((Object)(object)bowl.m_bossItem != (Object)null) ? ((Object)((Component)bowl.m_bossItem).gameObject).name : null),
				Count = Math.Max(1, bowl.m_bossItems),
				Place = place,
				PlacePrefab = placePrefab
			};
		}
	}
	internal static class Knowledge
	{
		private static readonly Dictionary<string, List<Source>> Where;

		private static readonly Dictionary<string, HashSet<string>> BiomesOf;

		private static readonly Dictionary<string, string> ModOf;

		private static readonly Dictionary<string, List<Source>> ComesFrom;

		private static readonly HashSet<string> PlacedByWorld;

		private static readonly Dictionary<Type, FieldInfo[]> DropTableFields;

		private static readonly Dictionary<string, List<Source>> SpawnPointLines;

		private static readonly Dictionary<string, List<Source>> DropLines;

		private static readonly MakerBook Made;

		private static readonly Dictionary<GameObject, string> ShownNames;

		private static readonly Dictionary<string, GameObject> Bosses;

		private static readonly Dictionary<string, List<GameObject>> Turned;

		private static readonly List<Summon> Altars;

		private static readonly UseBook Uses;

		private static readonly Dictionary<Type, FieldInfo[]> FuelFieldsByType;

		private const string JotunnGuid = "com.jotunn.jotunn";

		private static readonly Dictionary<(Type, string), PropertyInfo> Properties;

		private static FieldRef<List<SpawnSystem>> _spawnSystems;

		private static bool _spawnSystemsTried;

		private static readonly List<(GameObject Point, GameObject Creature, SpawnFacts Spawn)> SpawnPointsLeft;

		private static readonly Dictionary<Type, FieldInfo[]> ConversionFields;

		private static readonly List<(string Prefab, string Effect, string How)> GiverList;

		private static readonly Dictionary<Type, FieldInfo[]> EffectRefFields;

		public static string UpgradeStation { get; private set; }

		public static string UpgradeStationName { get; private set; }

		static Knowledge()
		{
			Where = new Dictionary<string, List<Source>>(StringComparer.Ordinal);
			BiomesOf = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
			ModOf = new Dictionary<string, string>(StringComparer.Ordinal);
			ComesFrom = new Dictionary<string, List<Source>>(StringComparer.Ordinal);
			PlacedByWorld = new HashSet<string>(StringComparer.Ordinal);
			DropTableFields = new Dictionary<Type, FieldInfo[]>();
			SpawnPointLines = new Dictionary<string, List<Source>>(StringComparer.Ordinal);
			DropLines = new Dictionary<string, List<Source>>(StringComparer.Ordinal);
			Made = new MakerBook();
			ShownNames = new Dictionary<GameObject, string>();
			Bosses = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
			Turned = new Dictionary<string, List<GameObject>>(StringComparer.Ordinal);
			Altars = new List<Summon>();
			Uses = new UseBook();
			FuelFieldsByType = new Dictionary<Type, FieldInfo[]>();
			Properties = new Dictionary<(Type, string), PropertyInfo>();
			SpawnPointsLeft = new List<(GameObject, GameObject, SpawnFacts)>();
			ConversionFields = new Dictionary<Type, FieldInfo[]>();
			GiverList = new List<(string, string, string)>();
			EffectRefFields = new Dictionary<Type, FieldInfo[]>();
			WorldCaches.Register("Knowledge", Begin);
		}

		public static IReadOnlyList<Source> SourceLines(string item)
		{
			if (!ComesFrom.TryGetValue(item, out var value))
			{
				return Array.Empty<Source>();
			}
			return value;
		}

		public static IReadOnlyList<Source> WhereLines(string prefab)
		{
			if (!Where.TryGetValue(prefab, out var value))
			{
				return Array.Empty<Source>();
			}
			return value;
		}

		public static string[] Biomes(string prefab)
		{
			if (!BiomesOf.TryGetValue(prefab, out var value))
			{
				return new string[0];
			}
			return value.ToArray();
		}

		public static bool IsPlacedByWorld(string prefab)
		{
			return PlacedByWorld.Contains(prefab);
		}

		public static string ModName(string name)
		{
			if (!ModOf.TryGetValue(name, out var value))
			{
				return "";
			}
			return value;
		}

		public static IReadOnlyList<Making> MadeOf(string item)
		{
			return Made.Of(item);
		}

		public static IReadOnlyList<Making> MadeAt(string station)
		{
			return Made.At(station);
		}

		public static IReadOnlyList<GameObject> TurnedFrom(string prefab)
		{
			if (!Turned.TryGetValue(prefab, out var value))
			{
				return Array.Empty<GameObject>();
			}
			return value;
		}

		public static GameObject MinedInside(GameObject broken)
		{
			if ((Object)(object)broken == (Object)null)
			{
				return null;
			}
			if (!((Object)(object)broken.GetComponent<MineRock5>() != (Object)null) && !((Object)(object)broken.GetComponent<MineRock>() != (Object)null))
			{
				return null;
			}
			return broken;
		}

		private static void Turns(GameObject prefab, List<Component> components)
		{
			foreach (Component component in components)
			{
				if ((Object)(object)component == (Object)null || (Object)(object)component.gameObject != (Object)(object)prefab)
				{
					continue;
				}
				TreeBase val = (TreeBase)(object)((component is TreeBase) ? component : null);
				if (val == null)
				{
					TreeLog val2 = (TreeLog)(object)((component is TreeLog) ? component : null);
					if (val2 == null)
					{
						Destructible val3 = (Destructible)(object)((component is Destructible) ? component : null);
						if (val3 != null)
						{
							Note(MinedInside(val3.m_spawnWhenDestroyed));
						}
					}
					else
					{
						Note(val2.m_subLogPrefab);
					}
				}
				else
				{
					Note(val.m_logPrefab);
				}
			}
			void Note(GameObject into)
			{
				if (!((Object)(object)into == (Object)null))
				{
					if (!Turned.TryGetValue(((Object)into).name, out var value))
					{
						value = (Turned[((Object)into).name] = new List<GameObject>());
					}
					if (!value.Contains(prefab))
					{
						value.Add(prefab);
					}
				}
			}
		}

		public static IEnumerable<Summon> Summons()
		{
			return Altars.Concat(Locations.Summons);
		}

		public static string BossOf(string key)
		{
			if (key == null || !Bosses.TryGetValue(key, out var value))
			{
				return null;
			}
			return ShownName(value);
		}

		public static string BossPrefabOf(string key)
		{
			if (key == null || !Bosses.TryGetValue(key, out var value))
			{
				return null;
			}
			return ((Object)value).name;
		}

		public static IReadOnlyList<UseGroup> UsesOf(string item)
		{
			return Uses.Of(item);
		}

		public static void Begin()
		{
			Where.Clear();
			BiomesOf.Clear();
			ModOf.Clear();
			ComesFrom.Clear();
			PlacedByWorld.Clear();
			GiverList.Clear();
			SpawnPointLines.Clear();
			DropLines.Clear();
			Made.Clear();
			ShownNames.Clear();
			Uses.Clear();
			Bosses.Clear();
			Altars.Clear();
			Turned.Clear();
			SpawnPointsLeft.Clear();
			UpgradeStation = null;
			UpgradeStationName = null;
		}

		public static void Read(GameObject prefab, List<Component> components)
		{
			long started = CatalogTiming.Start();
			try
			{
				SpawnPoints(prefab, components);
			}
			catch (Exception ex)
			{
				Failed("nests and spawn points", prefab, ex);
			}
			CatalogTiming.Add("spawn points", started);
			started = CatalogTiming.Start();
			try
			{
				Drops(prefab, components);
			}
			catch (Exception ex2)
			{
				Failed("drops", prefab, ex2);
			}
			CatalogTiming.Add("drops", started);
			started = CatalogTiming.Start();
			try
			{
				Makers(prefab, components);
			}
			catch (Exception ex3)
			{
				Failed("makers", prefab, ex3);
			}
			CatalogTiming.Add("makers", started);
			started = CatalogTiming.Start();
			try
			{
				Givers(prefab, components);
			}
			catch (Exception ex4)
			{
				Failed("status effect givers", prefab, ex4);
			}
			CatalogTiming.Add("givers", started);
			started = CatalogTiming.Start();
			try
			{
				UsesIn(prefab, components);
			}
			catch (Exception ex5)
			{
				Failed("uses of items", prefab, ex5);
			}
			CatalogTiming.Add("uses", started);
			started = CatalogTiming.Start();
			try
			{
				Turns(prefab, components);
			}
			catch (Exception ex6)
			{
				Failed("what things turn into", prefab, ex6);
			}
			CatalogTiming.Add("turns into", started);
			foreach (Component component in components)
			{
				Character val = (Character)(object)((component is Character) ? component : null);
				if (val != null && !string.IsNullOrEmpty(val.m_defeatSetGlobalKey) && !Bosses.ContainsKey(val.m_defeatSetGlobalKey))
				{
					Bosses[val.m_defeatSetGlobalKey] = prefab;
				}
				OfferingBowl val2 = (OfferingBowl)(object)((component is OfferingBowl) ? component : null);
				if (val2 != null)
				{
					Summon item = Summon.Of(val2, Shown(prefab), ((Object)prefab).name);
					if (item.Boss != null)
					{
						Altars.Add(item);
					}
				}
			}
		}

		private static void UsesIn(GameObject prefab, List<Component> components)
		{
			foreach (Component component in components)
			{
				Component val = component;
				if (val == null)
				{
					continue;
				}
				Piece val2 = (Piece)(object)((val is Piece) ? val : null);
				if (val2 == null)
				{
					CraftingStation val3 = (CraftingStation)(object)((val is CraftingStation) ? val : null);
					if (val3 == null)
					{
						MonsterAI val4 = (MonsterAI)(object)((val is MonsterAI) ? val : null);
						if (val4 != null && val4.m_consumeItems != null)
						{
							foreach (ItemDrop consumeItem in val4.m_consumeItems)
							{
								if ((Object)(object)consumeItem != (Object)null)
								{
									Uses.Add(((Object)((Component)consumeItem).gameObject).name, UseKind.EatenBy, ((Object)prefab).name, 0);
								}
							}
						}
					}
					else if (val3.m_upgrader && UpgradeStation == null)
					{
						UpgradeStation = ((Object)prefab).name;
						string text = CatalogBuilder.Localize(val3.m_name);
						UpgradeStationName = ((text.Length > 0) ? text : ((Object)prefab).name);
					}
				}
				else if (val2.m_enabled && val2.m_resources != null)
				{
					string place = (((Object)(object)val2.m_craftingStation != (Object)null) ? ((Object)((Component)val2.m_craftingStation).gameObject).name : null);
					Requirement[] resources = val2.m_resources;
					foreach (Requirement val5 in resources)
					{
						if ((Object)(object)val5?.m_resItem != (Object)null)
						{
							Uses.Add(((Object)((Component)val5.m_resItem).gameObject).name, UseKind.Builds, ((Object)prefab).name, val5.m_amount, place);
						}
					}
				}
				FieldInfo[] array = FuelFields(((object)component).GetType());
				for (int i = 0; i < array.Length; i++)
				{
					object value = array[i].GetValue(component);
					ItemDrop val6 = (ItemDrop)((value is ItemDrop) ? value : null);
					if (val6 != null)
					{
						if ((Object)(object)val6 != (Object)null)
						{
							Uses.Add(((Object)((Component)val6).gameObject).name, UseKind.Fuels, ((Object)prefab).name, 0);
						}
					}
					else
					{
						if (!(value is IEnumerable<ItemDrop> enumerable))
						{
							continue;
						}
						foreach (ItemDrop item in enumerable)
						{
							if ((Object)(object)item != (Object)null)
							{
								Uses.Add(((Object)((Component)item).gameObject).name, UseKind.Fuels, ((Object)prefab).name, 0);
							}
						}
					}
				}
			}
		}

		public static bool IsToldAsUse(Type type, FieldInfo field)
		{
			if (Array.IndexOf(FuelFields(type), field) < 0)
			{
				return Array.IndexOf(Conversions(type), field) >= 0;
			}
			return true;
		}

		private static FieldInfo[] FuelFields(Type type)
		{
			if (FuelFieldsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object) && type2 != typeof(MonoBehaviour))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						Type fieldType = fieldInfo.FieldType;
						if ((fieldType == typeof(ItemDrop) || fieldType == typeof(ItemDrop[]) || fieldType == typeof(List<ItemDrop>)) && fieldInfo.Name.IndexOf("fuel", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							list.Add(fieldInfo);
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception ex)
			{
				Faults.Skip("reading of a type's fields", type.Name, ex);
				list.Clear();
			}
			value = list.ToArray();
			FuelFieldsByType[type] = value;
			return value;
		}

		private static void Recipes()
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			foreach (Recipe recipe in instance.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || recipe.m_resources == null)
				{
					continue;
				}
				string place = (((Object)(object)recipe.m_craftingStation != (Object)null) ? ((Object)((Component)recipe.m_craftingStation).gameObject).name : "hand");
				int num = recipe.m_item.m_itemData?.m_shared?.m_maxQuality ?? 1;
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val in resources)
				{
					if (!((Object)(object)val?.m_resItem == (Object)null))
					{
						if (!val.m_upgraderResource)
						{
							Uses.Add(((Object)((Component)val.m_resItem).gameObject).name, UseKind.Crafts, ((Object)((Component)recipe.m_item).gameObject).name, val.m_amount, place);
						}
						else if (num > 1)
						{
							Uses.Add(((Object)((Component)val.m_resItem).gameObject).name, UseKind.UpgradesPastTop, ((Object)((Component)recipe.m_item).gameObject).name, val.GetAmount(num + 1));
						}
					}
				}
			}
		}

		private static void Failed(string what, GameObject prefab, Exception ex)
		{
			Faults.Skip(what, ((Object)prefab).name, ex);
		}

		public static IEnumerable<string> Finish(List<GameObject> prefabs)
		{
			Try("world spawners", WorldSpawners);
			Try("raids", Raids);
			Try("spawn points", TellSpawnPoints);
			Try("nests and spawn points", delegate
			{
				Merge(SpawnPointLines, delegate(string prefab, Source line)
				{
					Add(prefab, line.Text, line.Prefab);
				});
			});
			Try("vegetation", Vegetation);
			yield return "where things live";
			Try("drops", delegate
			{
				Merge(DropLines, From);
			});
			Try("recipes items go into", Recipes);
			Try("traders", Traders);
			yield return "what makes things";
			Try("Jotunn's registry", JotunnMods);
			yield return "which mod added what";
			foreach (string item in BundleMods(prefabs))
			{
				yield return item;
			}
		}

		private static void Merge(Dictionary<string, List<Source>> lines, Action<string, Source> add)
		{
			foreach (KeyValuePair<string, List<Source>> line in lines)
			{
				foreach (Source item in line.Value)
				{
					add(line.Key, item);
				}
			}
		}

		private static void Try(string what, Action act)
		{
			long started = CatalogTiming.Start();
			Stopwatch stopwatch = Stopwatch.StartNew();
			try
			{
				act();
				if (stopwatch.ElapsedMilliseconds >= 50)
				{
					Plugin.Note($"Scry read {what} in {stopwatch.ElapsedMilliseconds} ms.");
				}
			}
			catch (Exception ex)
			{
				if (Trouble.IsGameChange(ex))
				{
					Faults.Skip(what, "this world", ex);
				}
				else
				{
					Plugin.Log.LogWarning((object)("Scry could not read " + what + ", and leaves it out: " + ex.Message));
				}
			}
			CatalogTiming.Add(what, started);
		}

		private static void Each<T>(IEnumerable<T> list, string what, Func<T, string> name, Action<T> read) where T : class
		{
			if (list == null)
			{
				return;
			}
			foreach (T item in list)
			{
				if (item == null)
				{
					continue;
				}
				try
				{
					read(item);
				}
				catch (Exception ex)
				{
					string prefab;
					try
					{
						prefab = name(item);
					}
					catch
					{
						prefab = typeof(T).Name;
					}
					Faults.Skip(what, prefab, ex);
				}
			}
		}

		private static void From(string item, Source line)
		{
			if (!ComesFrom.TryGetValue(item, out var value))
			{
				value = (ComesFrom[item] = new List<Source>());
			}
			if (!value.Exists((Source l) => l.Text == line.Text))
			{
				value.Add(line);
			}
		}

		private static void Keep(Dictionary<string, List<Source>> lines, GameObject item, string line, string target)
		{
			if (!((Object)(object)item == (Object)null))
			{
				if (!lines.TryGetValue(((Object)item).name, out var value))
				{
					value = (lines[((Object)item).name] = new List<Source>());
				}
				value.Add(new Source(line, target));
			}
		}

		private static void JotunnMods()
		{
			Type registry;
			if (Chainloader.PluginInfos.TryGetValue("com.jotunn.jotunn", out var value) && !((Object)(object)((value != null) ? value.Instance : null) == (Object)null))
			{
				registry = ((object)value.Instance).GetType().Assembly.GetType("Jotunn.Utils.ModRegistry", throwOnError: false);
				if (!(registry == null))
				{
					Read("GetPrefabs", "Prefab");
					Read("GetItems", "ItemPrefab");
					Read("GetPieces", "PiecePrefab");
					Read("GetStatusEffects", "StatusEffect");
				}
			}
			static PropertyInfo Property(Type type, string name)
			{
				if (!Properties.TryGetValue((type, name), out var value2))
				{
					value2 = (Properties[(type, name)] = AccessTools.Property(type, name));
				}
				return value2;
			}
			void Read(string method, string property)
			{
				MethodInfo methodInfo = AccessTools.Method(registry, method, new Type[0], (Type[])null);
				if (methodInfo == null || !(methodInfo.Invoke(null, null) is IEnumerable enumerable))
				{
					return;
				}
				foreach (object item in enumerable)
				{
					if (item != null)
					{
						object? obj = Property(item.GetType(), "SourceMod")?.GetValue(item);
						BepInPlugin val = (BepInPlugin)((obj is BepInPlugin) ? obj : null);
						object? obj2 = Property(item.GetType(), property)?.GetValue(item);
						Object val2 = (Object)((obj2 is Object) ? obj2 : null);
						if (val != null && !(val2 == (Object)null))
						{
							ModOf[val2.name] = val.Name;
						}
					}
				}
			}
		}

		private static IEnumerable<string> BundleMods(List<GameObject> prefabs)
		{
			long started = CatalogTiming.Start();
			HashSet<string> wanted = new HashSet<string>(from p in prefabs
				where (Object)(object)p != (Object)null && !ModOf.ContainsKey(((Object)p).name) && Origins.Prefabs.Of(((Object)p).name) == Origin.Mod
				select ((Object)p).name.ToLowerInvariant());
			if (wanted.Count == 0)
			{
				yield break;
			}
			Dictionary<string, string> bundleOf = new Dictionary<string, string>();
			List<AssetBundle> list;
			try
			{
				list = AssetBundle.GetAllLoadedAssetBundles().ToList();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Scry could not read asset bundles: " + ex.Message));
				yield break;
			}
			foreach (AssetBundle item in list)
			{
				try
				{
					if ((Object)(object)item == (Object)null || item.isStreamedSceneAssetBundle)
					{
						continue;
					}
					string[] allAssetNames = item.GetAllAssetNames();
					for (int num = 0; num < allAssetNames.Length; num++)
					{
						string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(allAssetNames[num]);
						if (wanted.Contains(fileNameWithoutExtension) && !bundleOf.ContainsKey(fileNameWithoutExtension))
						{
							bundleOf[fileNameWithoutExtension] = Leaf(((Object)item).name);
						}
					}
					goto IL_018a;
				}
				catch (Exception ex2)
				{
					Faults.Skip("which mod added what", "a bundle", ex2);
					goto IL_018a;
				}
				IL_018a:
				CatalogTiming.Add("asset bundles", started);
				yield return "which mod added what: asset bundles";
				started = CatalogTiming.Start();
			}
			if (bundleOf.Count == 0)
			{
				yield break;
			}
			Dictionary<string, string> modOfBundle = new Dictionary<string, string>();
			HashSet<string> needed = new HashSet<string>(bundleOf.Values);
			foreach (PluginInfo item2 in Chainloader.PluginInfos.Values.ToList())
			{
				object obj;
				if (item2 == null)
				{
					obj = null;
				}
				else
				{
					BepInPlugin metadata = item2.Metadata;
					obj = ((metadata != null) ? metadata.Name : null);
				}
				string text = (string)obj;
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				started = CatalogTiming.Start();
				try
				{
					Assembly assembly = (((Object)(object)item2.Instance != (Object)null) ? ((object)item2.Instance).GetType().Assembly : null);
					if (assembly != null)
					{
						string[] allAssetNames = assembly.GetManifestResourceNames();
						for (int num = 0; num < allAssetNames.Length; num++)
						{
							string text2 = allAssetNames[num].ToLowerInvariant();
							foreach (string item3 in needed)
							{
								if (text2 == item3 || text2.EndsWith("." + item3))
								{
									modOfBundle[item3] = text;
								}
							}
						}
					}
					string directoryName = Path.GetDirectoryName(item2.Location);
					if (!string.IsNullOrEmpty(directoryName) && Directory.Exists(directoryName) && !IsPluginsRoot(directoryName))
					{
						string[] allAssetNames = Directory.GetFiles(directoryName, "*", SearchOption.AllDirectories);
						foreach (string path in allAssetNames)
						{
							string text3 = Path.GetFileName(path).ToLowerInvariant();
							string text4 = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
							if (needed.Contains(text3))
							{
								modOfBundle[text3] = text;
							}
							else if (needed.Contains(text4))
							{
								modOfBundle[text4] = text;
							}
						}
					}
				}
				catch (Exception ex3)
				{
					Faults.Skip("which mod added what", text, ex3);
				}
				CatalogTiming.Add("asset bundles", started);
				yield return "which mod added what: mods' files";
			}
			foreach (GameObject prefab in prefabs)
			{
				if (!((Object)(object)prefab == (Object)null) && !ModOf.ContainsKey(((Object)prefab).name) && bundleOf.TryGetValue(((Object)prefab).name.ToLowerInvariant(), out var value) && modOfBundle.TryGetValue(value, out var value2))
				{
					ModOf[((Object)prefab).name] = value2;
				}
			}
		}

		private static string Leaf(string bundleName)
		{
			string text = (bundleName ?? "").Replace('\\', '/');
			int num = text.LastIndexOf('/');
			return ((num >= 0) ? text.Substring(num + 1) : text).ToLowerInvariant();
		}

		private static bool IsPluginsRoot(string folder)
		{
			return string.Equals(Path.GetFullPath(folder).TrimEnd('\\', '/'), Path.GetFullPath(Paths.PluginPath).TrimEnd('\\', '/'), StringComparison.OrdinalIgnoreCase);
		}

		private static void Add(string prefab, string line, string target = null)
		{
			if (!string.IsNullOrEmpty(prefab))
			{
				if (!Where.TryGetValue(prefab, out var value))
				{
					value = (Where[prefab] = new List<Source>());
				}
				if (!value.Exists((Source l) => l.Text == line))
				{
					value.Add(new Source(line, target));
				}
			}
		}

		private unsafe static void AddBiomes(string prefab, Biome biome)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (!BiomesOf.TryGetValue(prefab, out var value))
			{
				value = (BiomesOf[prefab] = new HashSet<string>());
			}
			foreach (Biome item in Singles(biome))
			{
				value.Add(((object)(*(Biome*)(&item))/*cast due to .constrained prefix*/).ToString());
			}
		}

		private static IEnumerable<Biome> Singles(Biome biome)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			foreach (Biome value in Enum.GetValues(typeof(Biome)))
			{
				int num = (int)value;
				if (num != 0 && (num & (num - 1)) == 0 && (biome & value) != 0)
				{
					yield return value;
				}
			}
		}

		public static string BiomeName(string biome)
		{
			string text = CatalogBuilder.Localize("$biome_" + (biome ?? "").ToLowerInvariant());
			if (text.Length <= 0)
			{
				return Naming.FieldLabel(biome ?? "");
			}
			return text;
		}

		public unsafe static string BiomeNames(Biome biome)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			List<Biome> list = Singles(biome).ToList();
			int num = Enum.GetValues(typeof(Biome)).Cast<Biome>().Count(delegate(Biome b)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0002: Expected I4, but got Unknown
				int num2 = (int)b;
				return num2 != 0 && (num2 & (num2 - 1)) == 0;
			});
			if (list.Count == 0)
			{
				return "no biome";
			}
			if (list.Count >= num)
			{
				return "every biome";
			}
			return string.Join(", ", list.Select((Biome b) => BiomeName(((object)(*(Biome*)(&b))/*cast due to .constrained prefix*/).ToString())));
		}

		private static List<SpawnSystem> SpawnSystems()
		{
			if (!_spawnSystemsTried)
			{
				_spawnSystemsTried = true;
				FieldInfo fieldInfo = AccessTools.Field(typeof(SpawnSystem), "m_instances");
				if (fieldInfo != null)
				{
					_spawnSystems = AccessTools.StaticFieldRefAccess<List<SpawnSystem>>(fieldInfo);
				}
			}
			FieldRef<List<SpawnSystem>> spawnSystems = _spawnSystems;
			if (spawnSystems == null)
			{
				return null;
			}
			return spawnSystems.Invoke();
		}

		private static void WorldSpawners()
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			HashSet<SpawnSystemList> hashSet = new HashSet<SpawnSystemList>();
			foreach (SpawnSystem item in SpawnSystems() ?? new List<SpawnSystem>())
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				foreach (SpawnSystemList spawnList in item.m_spawnLists)
				{
					if ((Object)(object)spawnList != (Object)null)
					{
						hashSet.Add(spawnList);
					}
				}
			}
			if (hashSet.Count == 0)
			{
				SpawnSystemList[] array = Resources.FindObjectsOfTypeAll<SpawnSystemList>();
				foreach (SpawnSystemList val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						Scene scene = ((Component)val).gameObject.scene;
						if (((Scene)(ref scene)).IsValid())
						{
							hashSet.Add(val);
						}
					}
				}
			}
			foreach (SpawnSystemList item2 in hashSet)
			{
				Each(item2.m_spawners, "world spawns", (SpawnData d) => (d.m_name.Length <= 0) ? ((!((Object)(object)d.m_prefab != (Object)null)) ? "a spawn" : ((Object)d.m_prefab).name) : d.m_name, delegate(SpawnData data)
				{
					//IL_0025: Unknown result type (might be due to invalid IL or missing references)
					//IL_0043: Unknown result type (might be due to invalid IL or missing references)
					if (!((Object)(object)data.m_prefab == (Object)null) && data.m_enabled)
					{
						string name = ((Object)data.m_prefab).name;
						AddBiomes(name, data.m_biome);
						PlacedByWorld.Add(name);
						SpawnFacts spawn = new SpawnFacts
						{
							Biomes = BiomeNames(data.m_biome),
							AtNight = data.m_spawnAtNight,
							AtDay = data.m_spawnAtDay,
							MinLevel = data.m_minLevel,
							MaxLevel = data.m_maxLevel,
							GroupMin = data.m_groupSizeMin,
							GroupMax = data.m_groupSizeMax,
							InForest = data.m_inForest,
							OutsideForest = data.m_outsideForest,
							Weather = (data.m_requiredEnvironments ?? new List<string>()).Where((string e) => !string.IsNullOrEmpty(e)).Select(Naming.FieldLabel).ToArray(),
							Keys = new string[1] { data.m_requiredGlobalKey }
						};
						Add(name, SpawnWords.Line("Spawns in", spawn, BossOf), BossPrefabOf(data.m_requiredGlobalKey));
					}
				});
			}
		}

		private static void Raids()
		{
			List<RandomEvent> list = RandEventSystem.instance?.m_events;
			if (list == null)
			{
				return;
			}
			bool byPlayer = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)17);
			Each(list, "raids", (RandomEvent r) => r.m_name, delegate(RandomEvent raid)
			{
				//IL_009b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				if (raid.m_spawn == null || !raid.m_enabled)
				{
					return;
				}
				string text = CatalogBuilder.Localize(raid.m_startMessage);
				string text2 = "Comes in the raid \"" + ((text.Length > 0) ? text : raid.m_name) + "\"";
				bool flag = byPlayer && (Count<string>(raid.m_altRequiredPlayerKeysAny) > 0 || Count<string>(raid.m_altRequiredPlayerKeysAll) > 0 || Count<ItemDrop>(raid.m_altRequiredKnownItems) > 0 || Count<ItemDrop>(raid.m_altRequiredNotKnownItems) > 0 || Count<string>(raid.m_altNotRequiredPlayerKeys) > 0);
				SpawnFacts spawnFacts = new SpawnFacts
				{
					Biomes = (((int)raid.m_biome != 0) ? BiomeNames(raid.m_biome) : ""),
					Keys = ((flag || raid.m_requiredGlobalKeys == null) ? new string[0] : raid.m_requiredGlobalKeys.ToArray()),
					NotKeys = ((flag || raid.m_notRequiredGlobalKeys == null) ? new string[0] : raid.m_notRequiredGlobalKeys.ToArray())
				};
				string text3 = SpawnWords.Line((spawnFacts.Biomes.Length > 0) ? (text2 + ", in") : text2, spawnFacts, BossOf);
				if (flag)
				{
					text3 += ", for a player whose own progress calls for it";
				}
				foreach (SpawnData item in raid.m_spawn)
				{
					if (!((Object)(object)item?.m_prefab == (Object)null))
					{
						Add(((Object)item.m_prefab).name, text3, BossPrefabOf(spawnFacts.Keys.FirstOrDefault()));
					}
				}
			});
			static int Count<T>(List<T> keys)
			{
				return keys?.Count ?? 0;
			}
		}

		private static void TellSpawnPoints()
		{
			foreach (var (val, item, spawn) in SpawnPointsLeft)
			{
				Keep(SpawnPointLines, item, SpawnWords.Line("In dungeons or locations, from the spawn point " + Shown(val), spawn, BossOf), ((Object)val).name);
			}
			SpawnPointsLeft.Clear();
		}

		private static void SpawnPoints(GameObject prefab, List<Component> components)
		{
			foreach (Component component in components)
			{
				SpawnArea val = (SpawnArea)(object)((component is SpawnArea) ? component : null);
				if (val == null || val.m_prefabs == null)
				{
					continue;
				}
				foreach (SpawnData prefab2 in val.m_prefabs)
				{
					if (!((Object)(object)prefab2?.m_prefab == (Object)null))
					{
						Keep(SpawnPointLines, prefab2.m_prefab, "Comes from " + Shown(prefab) + ", " + SpawnWords.Stars(prefab2.m_minLevel, prefab2.m_maxLevel), ((Object)prefab).name);
					}
				}
			}
			foreach (Component component2 in components)
			{
				CreatureSpawner val2 = (CreatureSpawner)(object)((component2 is CreatureSpawner) ? component2 : null);
				if (val2 != null && !((Object)(object)val2.m_creaturePrefab == (Object)null))
				{
					SpawnFacts spawnFacts = new SpawnFacts();
					spawnFacts.AtNight = val2.m_spawnAtNight;
					spawnFacts.AtDay = val2.m_spawnAtDay;
					spawnFacts.MinLevel = val2.m_minLevel;
					spawnFacts.MaxLevel = val2.m_maxLevel;
					spawnFacts.Keys = new string[1] { val2.m_requiredGlobalKey };
					spawnFacts.NotKeys = new string[1] { val2.m_blockingGlobalKey };
					SpawnFacts item = spawnFacts;
					SpawnPointsLeft.Add((prefab, val2.m_creaturePrefab, item));
				}
			}
		}

		private static void Vegetation()
		{
			List<ZoneVegetation> list = ZoneSystem.instance?.m_vegetation;
			if (list == null)
			{
				return;
			}
			Each(list, "vegetation", (ZoneVegetation v) => v.m_name, delegate(ZoneVegetation veg)
			{
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)veg.m_prefab == (Object)null) && veg.m_enable)
				{
					string name = ((Object)veg.m_prefab).name;
					AddBiomes(name, veg.m_biome);
					PlacedByWorld.Add(name);
					string text = "Grows in " + BiomeNames(veg.m_biome);
					if (veg.m_minAltitude > -1000f || veg.m_maxAltitude < 1000f)
					{
						text += ((veg.m_maxAltitude < 1000f) ? (", " + DropWords.Range(Mathf.RoundToInt(veg.m_minAltitude), Mathf.RoundToInt(veg.m_maxAltitude)) + " m up") : $", from {Mathf.RoundToInt(veg.m_minAltitude)} m up");
					}
					if (veg.m_minOceanDepth > 0f || veg.m_maxOceanDepth > 0f)
					{
						text += ", in the sea";
					}
					if (veg.m_inForest)
					{
						text += ", in forests";
					}
					string text2 = SpawnWords.Group(veg.m_groupSizeMin, veg.m_groupSizeMax);
					if (text2 != null)
					{
						text = text + ", " + text2;
					}
					Add(name, text);
				}
			});
		}

		private static void Drops(GameObject prefab, List<Component> components)
		{
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			foreach (Component component in components)
			{
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				CharacterDrop val = (CharacterDrop)(object)((component is CharacterDrop) ? component : null);
				if (val != null)
				{
					if (val.m_drops == null)
					{
						continue;
					}
					foreach (Drop drop in val.m_drops)
					{
						if (!((Object)(object)drop?.m_prefab == (Object)null))
						{
							string text = DropWords.CreatureAmount(drop.m_amountMin, drop.m_amountMax, drop.m_onePerPlayer);
							string text2 = ((drop.m_chance < 1f) ? $" ({Mathf.RoundToInt(drop.m_chance * 100f)}%)" : "");
							Keep(DropLines, drop.m_prefab, "Dropped by " + Shown(prefab) + ", " + text + text2, ((Object)prefab).name);
						}
					}
					continue;
				}
				Pickable val2 = (Pickable)(object)((component is Pickable) ? component : null);
				if (val2 != null)
				{
					Keep(DropLines, val2.m_itemPrefab, "Picked from " + Shown(prefab), ((Object)prefab).name);
				}
				Plant val3 = (Plant)(object)((component is Plant) ? component : null);
				if (val3 != null && val3.m_grownPrefabs != null)
				{
					GameObject[] grownPrefabs = val3.m_grownPrefabs;
					foreach (GameObject item in grownPrefabs)
					{
						Keep(DropLines, item, "Grows from " + Shown(prefab), ((Object)prefab).name);
					}
				}
				FieldInfo[] array = DropTables(((object)component).GetType());
				if (array.Length == 0)
				{
					continue;
				}
				string text3 = null;
				FieldInfo[] array2 = array;
				for (int i = 0; i < array2.Length; i++)
				{
					object? value = array2[i].GetValue(component);
					DropTable val4 = (DropTable)((value is DropTable) ? value : null);
					if (val4 == null || val4.m_drops == null)
					{
						continue;
					}
					text3 = text3 ?? (FromVerb(component) + " " + Shown(prefab));
					foreach (DropData drop2 in val4.m_drops)
					{
						Keep(DropLines, drop2.m_item, text3, ((Object)prefab).name);
					}
				}
			}
		}

		private static void Makers(GameObject prefab, List<Component> components)
		{
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			foreach (Component component in components)
			{
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				FieldInfo[] array = Conversions(((object)component).GetType());
				for (int i = 0; i < array.Length; i++)
				{
					if (!(array[i].GetValue(component) is IEnumerable enumerable))
					{
						continue;
					}
					foreach (object item in enumerable)
					{
						if (item != null)
						{
							Type type = item.GetType();
							object? obj = type.GetField("m_from")?.GetValue(item);
							ItemDrop val = (ItemDrop)((obj is ItemDrop) ? obj : null);
							object? obj2 = type.GetField("m_to")?.GetValue(item);
							ItemDrop val2 = (ItemDrop)((obj2 is ItemDrop) ? obj2 : null);
							if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null))
							{
								int makes = ((!(type.GetField("m_producedItems")?.GetValue(item) is int num) || num <= 1) ? 1 : num);
								Making making = new Making
								{
									Station = ((Object)prefab).name,
									Output = ((Object)((Component)val2).gameObject).name,
									Makes = makes
								};
								making.Inputs.Add((((Object)((Component)val).gameObject).name, 1));
								Made.Add(making);
								Uses.Add(((Object)((Component)val).gameObject).name, UseKind.TurnsInto, ((Object)((Component)val2).gameObject).name, 0, ((Object)prefab).name);
							}
						}
					}
				}
				Beehive val3 = (Beehive)(object)((component is Beehive) ? component : null);
				if (val3 != null && (Object)(object)val3.m_honeyItem != (Object)null)
				{
					string text = (((int)val3.m_biome != 0) ? (", in " + BiomeNames(val3.m_biome)) : "");
					Keep(DropLines, ((Component)val3.m_honeyItem).gameObject, $"Made by {Shown(prefab)}, one every {Naming.Duration(val3.m_secPerUnit)}, holding up to {val3.m_maxHoney}{text}", ((Object)prefab).name);
				}
				SapCollector val4 = (SapCollector)(object)((component is SapCollector) ? component : null);
				if (val4 != null && (Object)(object)val4.m_spawnItem != (Object)null)
				{
					Keep(DropLines, ((Component)val4.m_spawnItem).gameObject, $"Made by {Shown(prefab)}, one every {Naming.Duration(val4.m_secPerUnit)}, holding up to {val4.m_maxLevel}", ((Object)prefab).name);
				}
				Incinerator val5 = (Incinerator)(object)((component is Incinerator) ? component : null);
				if (val5 == null || val5.m_conversions == null)
				{
					continue;
				}
				foreach (IncineratorConversion conversion in val5.m_conversions)
				{
					if ((Object)(object)conversion?.m_result == (Object)null || conversion.m_requirements == null)
					{
						continue;
					}
					Making making2 = new Making
					{
						Station = ((Object)prefab).name,
						Output = ((Object)((Component)conversion.m_result).gameObject).name,
						Makes = Math.Max(1, conversion.m_resultAmount),
						AnyOne = conversion.m_requireOnlyOneIngredient
					};
					foreach (Requirement requirement in conversion.m_requirements)
					{
						if (!((Object)(object)requirement?.m_resItem == (Object)null))
						{
							making2.Inputs.Add((((Object)((Component)requirement.m_resItem).gameObject).name, Math.Max(1, requirement.m_amount)));
							Uses.Add(((Object)((Component)requirement.m_resItem).gameObject).name, UseKind.TurnsInto, making2.Output, 0, ((Object)prefab).name);
						}
					}
					Made.Add(making2);
				}
			}
		}

		private static string FromVerb(Component component)
		{
			if (!(component is TreeBase))
			{
				if (!(component is TreeLog))
				{
					if (!(component is MineRock) && !(component is MineRock5))
					{
						if (!(component is Container))
						{
							if (!(component is Pickable))
							{
								if (component is DropOnDestroyed)
								{
									return "Broken out of";
								}
								return "Comes out of";
							}
							return "Also picked from";
						}
						return "Found in";
					}
					return "Mined from";
				}
				return "Chopped from";
			}
			return "Felled from";
		}

		private static void Traders()
		{
			Each(Resources.FindObjectsOfTypeAll<Trader>(), "traders", (Trader t) => ((Object)t).name, delegate(Trader trader)
			{
				if (trader.m_items == null)
				{
					return;
				}
				string text = CatalogBuilder.Localize(trader.m_name);
				if (text.Length == 0)
				{
					text = ((Object)((Component)trader).gameObject).name;
				}
				string prefab = ((Object)((Component)((Component)trader).transform.root).gameObject).name.Replace("(Clone)", "").Trim();
				foreach (TradeItem item in trader.m_items)
				{
					if (!((Object)(object)item?.m_prefab == (Object)null))
					{
						string text2 = ((item.m_stack > 1) ? $"{item.m_stack} for " : "");
						string text3 = (string.IsNullOrEmpty(item.m_requiredGlobalKey) ? "" : (", " + SpawnWords.Once(item.m_requiredGlobalKey, BossOf)));
						From(((Object)((Component)item.m_prefab).gameObject).name, new Source($"Sold by {text}, {text2}{item.m_price} coins{text3}", prefab));
					}
				}
			});
		}

		private static FieldInfo[] Conversions(Type type)
		{
			if (ConversionFields.TryGetValue(type, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object) && type2 != typeof(MonoBehaviour))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						Type type3 = (fieldInfo.FieldType.IsArray ? fieldInfo.FieldType.GetElementType() : ((fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(List<>)) ? fieldInfo.FieldType.GetGenericArguments()[0] : null));
						if (!(type3 == null) && type3.GetField("m_from")?.FieldType == typeof(ItemDrop) && type3.GetField("m_to")?.FieldType == typeof(ItemDrop))
						{
							list.Add(fieldInfo);
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception ex)
			{
				Faults.Skip("reading of a type's fields", type.Name, ex);
				list.Clear();
			}
			value = list.ToArray();
			ConversionFields[type] = value;
			return value;
		}

		public static IReadOnlyList<(string Prefab, string Effect, string How)> Givers()
		{
			return GiverList;
		}

		private static void Givers(GameObject prefab, List<Component> components)
		{
			foreach (Component component in components)
			{
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				Note(component);
				SharedData val = ((ItemDrop)(((component is ItemDrop) ? component : null)?)).m_itemData?.m_shared;
				if (val != null)
				{
					Note(val);
					if (val.m_attack != null)
					{
						Note(val.m_attack);
					}
					if (val.m_secondaryAttack != null)
					{
						Note(val.m_secondaryAttack);
					}
				}
			}
			void Note(object owner)
			{
				FieldInfo[] array = EffectRefs(owner.GetType());
				foreach (FieldInfo fieldInfo in array)
				{
					string text = null;
					object value = fieldInfo.GetValue(owner);
					StatusEffect val2 = (StatusEffect)((value is StatusEffect) ? value : null);
					if (val2 != null && (Object)(object)val2 != (Object)null)
					{
						text = ((Object)val2).name;
					}
					else if (value is string { Length: >0 } text2)
					{
						text = text2;
					}
					if (text != null)
					{