Decompiled source of Fate Forge v0.4.8

BepInEx/plugins/FateForge/FateForge.dll

Decompiled 7 hours 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.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using FateForge.Core;
using HarmonyLib;
using Jotunn;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using SoftReferenceableAssets;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FateForge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.8.0")]
[assembly: AssemblyInformationalVersion("0.4.8")]
[assembly: AssemblyProduct("FateForge")]
[assembly: AssemblyTitle("FateForge")]
[assembly: AssemblyVersion("0.4.8.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 FateForge.Mod
{
	public sealed class EpicLootInspection
	{
		public bool Special;

		public bool Magic;

		public bool Unidentified;

		public bool Available;

		public string Reason = "";

		public string Summary = "";

		public string DisplaySuffix = "";

		public string MagicJson = "";

		public string EnchantCostsJson = "";

		public string SacrificeProductsJson = "";

		public string Fingerprint = "";

		public int Rarity = -1;

		public int? WagerRarity;
	}
	public sealed class EpicLootEnchantOption
	{
		public int Rarity;

		public string Display = "";

		public string Color = "#FFFFFF";

		public string CostSummary = "";

		public string CostsJson = "";

		public string BlockReason = "";

		public bool Available;
	}
	public static class EpicLootBridge
	{
		public const string PluginId = "randyknapp.mods.epicloot";

		public const string InspectedVersion = "0.14.5";

		private static readonly string[] RarityNames;

		private static readonly string[] QueryNames;

		private static Assembly _assembly;

		private static Type _api;

		private static Type _validatedApi;

		private static bool _resolved;

		private static string _pluginVersion;

		private static readonly object ApiLock;

		private static readonly MethodInfo ShallowCopy;

		private static readonly Dictionary<(Type, string, Type, Type, Type), MethodInfo> Methods;

		private static Type ApiType
		{
			get
			{
				ResolveApi();
				return _api;
			}
		}

		private static bool AssemblyPresent
		{
			get
			{
				ResolveApi();
				return _assembly != null;
			}
		}

		public static bool IsInstalled => AssemblyPresent;

		public static bool SupportedApiPresent
		{
			get
			{
				if (!AssemblyPresent)
				{
					return false;
				}
				try
				{
					Ready();
					return true;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static Assembly ConfigurationAssembly => Ready().Assembly;

		public static string Status
		{
			get
			{
				if (!AssemblyPresent)
				{
					return "Epic Loot optional: not loaded. Ordinary gambling is available.";
				}
				try
				{
					Ready();
					return "Epic Loot " + _pluginVersion + " detected (public API 1). Existing magic offerings include configured enchant costs and a gear rarity premium; individual effects receive no invented bonus.";
				}
				catch (Exception ex)
				{
					return ex.GetBaseException().Message;
				}
			}
		}

		static EpicLootBridge()
		{
			RarityNames = new string[6] { "Magic", "Rare", "Epic", "Legendary", "Mythic", "Ancient" };
			QueryNames = new string[7] { "IsMagicItem", "IsUnidentified", "IsEpicLootItem", "IsRunestone", "IsShardStone", "IsMagicCraftingMaterial", "CanBeMagicItem" };
			ApiLock = new object();
			ShallowCopy = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
			Methods = new Dictionary<(Type, string, Type, Type, Type), MethodInfo>();
			AppDomain.CurrentDomain.AssemblyLoad += delegate(object _, AssemblyLoadEventArgs args)
			{
				if (args.LoadedAssembly.GetName().Name != "EpicLoot")
				{
					return;
				}
				lock (ApiLock)
				{
					_resolved = false;
					_validatedApi = null;
				}
			};
		}

		private static void ResolveApi()
		{
			lock (ApiLock)
			{
				if (!_resolved)
				{
					_assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly x) => string.Equals(x.GetName().Name, "EpicLoot", StringComparison.Ordinal));
					_api = _assembly?.GetType("EpicLoot.API", throwOnError: false);
					_resolved = true;
				}
			}
		}

		private static MethodInfo Required(Type api, string name, Type result, params Type[] parameters)
		{
			if (api == null || parameters.Length > 2)
			{
				throw new MissingMethodException("Unsupported Epic Loot public endpoint: " + name);
			}
			(Type, string, Type, Type, Type) key = (api, name, result, (parameters.Length != 0) ? parameters[0] : null, (parameters.Length > 1) ? parameters[1] : null);
			lock (ApiLock)
			{
				if (Methods.TryGetValue(key, out var value))
				{
					return value;
				}
				MethodInfo method = api.GetMethod(name, BindingFlags.Static | BindingFlags.Public, null, parameters, null);
				if (method == null || method.ReturnType != result)
				{
					throw new MissingMethodException("Unsupported Epic Loot public endpoint: " + name);
				}
				Methods.Add(key, method);
				return method;
			}
		}

		private static T Query<T>(Type api, string name, ItemData item)
		{
			return (T)Required(api, name, typeof(T), typeof(ItemData)).Invoke(null, new object[1] { item });
		}

		private static T Scalar<T>(Type api, string name)
		{
			return (T)Required(api, name, typeof(T)).Invoke(null, null);
		}

		private static string RarityText(Type api, string name, int rarity)
		{
			return (string)Required(api, name, typeof(string), typeof(int)).Invoke(null, new object[1] { rarity });
		}

		private static string EnchantCosts(Type api, ItemData item, int rarity)
		{
			return (string)Required(api, "GetEnchantCostsJson", typeof(string), typeof(ItemData), typeof(int)).Invoke(null, new object[2] { item, rarity });
		}

		private static string Localize(string value)
		{
			if (Localization.instance != null)
			{
				return Localization.instance.Localize(value);
			}
			return value;
		}

		public static void ValidateContract(Type api)
		{
			if (api == null)
			{
				throw new MissingMemberException("Epic Loot public API is unavailable.");
			}
			string[] queryNames = QueryNames;
			foreach (string name in queryNames)
			{
				Required(api, name, typeof(bool), typeof(ItemData));
			}
			queryNames = new string[2] { "GetMagicItemJson", "GetSacrificeProductsJson" };
			foreach (string name2 in queryNames)
			{
				Required(api, name2, typeof(string), typeof(ItemData));
			}
			queryNames = new string[2] { "GetApiVersion", "GetRarityCount" };
			foreach (string name3 in queryNames)
			{
				Required(api, name3, typeof(int));
			}
			Required(api, "GetPluginVersion", typeof(string));
			Required(api, "TryGetRarity", typeof(bool), typeof(ItemData), typeof(int).MakeByRefType());
			Required(api, "GetEnchantCostsJson", typeof(string), typeof(ItemData), typeof(int));
			queryNames = new string[2] { "GetRarityDisplayNameByIndex", "GetRarityColorByIndex" };
			foreach (string name4 in queryNames)
			{
				Required(api, name4, typeof(string), typeof(int));
			}
		}

		internal static string ValidateRuntimeContract(Type api)
		{
			string text = "unknown version";
			try
			{
				text = Scalar<string>(api, "GetPluginVersion");
				if (string.IsNullOrWhiteSpace(text) || text.Length > 128)
				{
					throw new NotSupportedException("Invalid public plugin version.");
				}
				ValidateContract(api);
				int num = Scalar<int>(api, "GetApiVersion");
				if (num != 1)
				{
					throw new NotSupportedException("Public API " + num + " has not been reviewed; API 1 is required.");
				}
				if (Scalar<int>(api, "GetRarityCount") != 6)
				{
					throw new NotSupportedException("Unsupported public rarity count.");
				}
				for (int i = 0; i < RarityNames.Length; i++)
				{
					if (RarityText(api, "GetRarityDisplayNameByIndex", i) != "$mod_epicloot_" + RarityNames[i])
					{
						throw new NotSupportedException("Unsupported public rarity order at index " + i + ".");
					}
				}
				return text;
			}
			catch (Exception ex)
			{
				throw new NotSupportedException("Epic Loot " + text + " is incompatible with Fate Forge: " + ex.GetBaseException().Message + " Pricing is stopped to avoid incorrect fallback values.");
			}
		}

		private static Type Ready()
		{
			Type apiType = ApiType;
			if ((object)_validatedApi != apiType || apiType == null)
			{
				_pluginVersion = ValidateRuntimeContract(apiType);
				_validatedApi = apiType;
			}
			return apiType;
		}

		internal static void EnsureCompatibleIfPresent()
		{
			if (AssemblyPresent)
			{
				Ready();
			}
		}

		internal static int? PublicRarity(ItemData item)
		{
			Type api = Ready();
			object[] array = new object[2]
			{
				Detached(item),
				-1
			};
			if (!(bool)Required(api, "TryGetRarity", typeof(bool), typeof(ItemData), typeof(int).MakeByRefType()).Invoke(null, array))
			{
				return null;
			}
			int num = (int)array[1];
			if (num < 0 || num >= Scalar<int>(api, "GetRarityCount"))
			{
				return null;
			}
			return num;
		}

		internal static bool TryMaterial(ItemData item, out int rarity, out string kind, out IReadOnlyList<EpicLootCost> sacrifice)
		{
			rarity = -1;
			kind = "";
			sacrifice = Array.Empty<EpicLootCost>();
			Type api = Ready();
			ItemData val = Detached(item);
			if (Query<bool>(api, "IsShardStone", val))
			{
				kind = "stone";
			}
			else if (Query<bool>(api, "IsRunestone", val))
			{
				kind = "rune";
			}
			else
			{
				if (!Query<bool>(api, "IsMagicCraftingMaterial", val))
				{
					return false;
				}
				kind = "material";
			}
			object[] array = new object[2] { val, -1 };
			if (!(bool)Required(api, "TryGetRarity", typeof(bool), typeof(ItemData), typeof(int).MakeByRefType()).Invoke(null, array))
			{
				return false;
			}
			rarity = (int)array[1];
			if (rarity < 0 || rarity >= Scalar<int>(api, "GetRarityCount") || rarity > 99)
			{
				return false;
			}
			if (kind == "stone")
			{
				sacrifice = EpicLootMetadata.ParseCosts(Query<string>(api, "GetSacrificeProductsJson", val));
			}
			return true;
		}

		public static EpicLootInspection Inspect(ItemData item)
		{
			EpicLootInspection epicLootInspection = new EpicLootInspection();
			if (item?.m_shared == null)
			{
				epicLootInspection.Reason = "Missing item data.";
				return epicLootInspection;
			}
			Dictionary<string, string> dictionary = item.m_customData ?? new Dictionary<string, string>();
			epicLootInspection.Available = true;
			try
			{
				if (AssemblyPresent)
				{
					if (dictionary.Count > 256 || dictionary.Any((KeyValuePair<string, string> x) => x.Key == null || x.Key.Length > 256 || (x.Value?.Length ?? 0) > 65536) || dictionary.Sum((KeyValuePair<string, string> x) => (long)x.Key.Length + (long)(x.Value?.Length ?? 0)) > 262144)
					{
						throw new InvalidOperationException("Metadata inspection bounds exceeded.");
					}
					Type api = Ready();
					ItemData val = Detached(item);
					epicLootInspection.Unidentified = Query<bool>(api, "IsUnidentified", val);
					epicLootInspection.Magic = epicLootInspection.Unidentified || Query<bool>(api, "IsMagicItem", val);
					bool flag = Query<bool>(api, "IsRunestone", val);
					bool flag2 = Query<bool>(api, "IsShardStone", val);
					epicLootInspection.Special = epicLootInspection.Magic || flag || flag2 || Query<bool>(api, "IsMagicCraftingMaterial", val) || Query<bool>(api, "IsEpicLootItem", val);
					if (epicLootInspection.Special)
					{
						object[] array = new object[2] { val, -1 };
						bool flag3 = (bool)Required(api, "TryGetRarity", typeof(bool), typeof(ItemData), typeof(int).MakeByRefType()).Invoke(null, array);
						int num = Scalar<int>(api, "GetRarityCount");
						epicLootInspection.Rarity = (flag3 ? ((int)array[1]) : (-1));
						if (num < 1 || num > 100 || (flag3 && (epicLootInspection.Rarity < 0 || epicLootInspection.Rarity >= num)))
						{
							throw new InvalidOperationException("Unsupported Epic Loot rarity bounds.");
						}
						string text = (flag3 ? Localize(RarityText(api, "GetRarityDisplayNameByIndex", epicLootInspection.Rarity)) : "Epic Loot");
						epicLootInspection.DisplaySuffix = " [" + (epicLootInspection.Unidentified ? "Unidentified" : (text + (flag ? " rune" : (flag2 ? " shard" : "")))) + "]";
						if (epicLootInspection.Unidentified)
						{
							epicLootInspection.Summary = "Unidentified magic item; effects are hidden.";
						}
						else if (epicLootInspection.Magic)
						{
							epicLootInspection.MagicJson = Query<string>(api, "GetMagicItemJson", val) ?? "";
							EpicLootMagicData val2 = EpicLootMetadata.ParseMagic(epicLootInspection.MagicJson);
							if (val2.IsUnidentified || !flag3 || val2.Rarity != epicLootInspection.Rarity)
							{
								throw new InvalidOperationException("Epic Loot identity metadata mismatch.");
							}
							epicLootInspection.Summary = EpicLootMetadata.DescribeMagic(val2);
						}
						else
						{
							epicLootInspection.Summary = "Epic Loot material: registered base or progression estimate.";
						}
						if (epicLootInspection.Magic && flag3 && Query<bool>(api, "CanBeMagicItem", val))
						{
							epicLootInspection.WagerRarity = epicLootInspection.Rarity;
						}
					}
				}
				if (!epicLootInspection.Special && (EpicLootMetadata.HasUnreviewedCustomData((IEnumerable<KeyValuePair<string, string>>)dictionary, SupportedApiPresent) || item.m_shared.m_name.StartsWith("$mod_epicloot", StringComparison.Ordinal)))
				{
					epicLootInspection.Special = true;
					epicLootInspection.DisplaySuffix = " [Custom item]";
					epicLootInspection.Reason = "Existing custom item uses its registered base or progression estimate; custom effects add no guessed value.";
					epicLootInspection.Summary = epicLootInspection.Reason;
				}
			}
			catch (Exception)
			{
				epicLootInspection.Special = true;
				epicLootInspection.WagerRarity = null;
				epicLootInspection.MagicJson = "";
				epicLootInspection.Reason = "Epic Loot details unavailable; using the item's base estimate without a magic bonus.";
				epicLootInspection.Summary = (epicLootInspection.Unidentified ? "Unidentified magic item; effects are hidden. Base estimate only." : epicLootInspection.Reason);
			}
			epicLootInspection.Fingerprint = EpicLootMetadata.Fingerprint(epicLootInspection.MagicJson, epicLootInspection.EnchantCostsJson, epicLootInspection.SacrificeProductsJson, (IEnumerable<KeyValuePair<string, string>>)(item.m_customData ?? new Dictionary<string, string>()));
			return epicLootInspection;
		}

		public static PriceResult Price(Economy economy, ItemData item)
		{
			int? wagerRarity;
			return Price(economy, item, out wagerRarity);
		}

		public static PriceResult PriceCanonicalReward(Economy economy, GameObject registered, int quality = 1)
		{
			ItemData val = (((Object)(object)registered == (Object)null) ? null : registered.GetComponent<ItemDrop>()?.m_itemData);
			if (val?.m_shared == null || (Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.GetItemPrefab(GameAdapter.PrefabName(registered)) != registered)
			{
				return PriceResult.Unavailable("Missing or changed canonical reward definition.");
			}
			ItemData val2 = Detached(val);
			val2.m_dropPrefab = registered;
			val2.m_quality = quality;
			return Price(economy, val2);
		}

		public static PriceResult Price(Economy economy, ItemData item, out int? wagerRarity)
		{
			EpicLootInspection epicLootInspection = Inspect(item);
			wagerRarity = epicLootInspection.WagerRarity;
			if (!epicLootInspection.Available)
			{
				return PriceResult.Unavailable(epicLootInspection.Reason);
			}
			string text = GameAdapter.PrefabName(item);
			if (EpicSupplementValuation.IsFixedRelic(text))
			{
				wagerRarity = null;
			}
			GameObject val = ((wagerRarity.HasValue && (Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(text) : null);
			if ((Object)(object)val == (Object)null)
			{
				wagerRarity = null;
			}
			PriceResult val2 = (wagerRarity.HasValue ? PriceForRarity(economy, val, item.m_quality, wagerRarity) : economy.Price(text, item.m_quality));
			if (val2.Available && epicLootInspection.Special && !wagerRarity.HasValue)
			{
				return PriceResult.Priced(val2.UnitValue, "Existing custom/special item uses its base value; no effect bonus. " + val2.Reason);
			}
			return val2;
		}

		public static PriceResult PriceForRarity(Economy economy, GameObject registered, int quality, int? rarity)
		{
			ItemData val = (((Object)(object)registered == (Object)null) ? null : registered.GetComponent<ItemDrop>()?.m_itemData);
			if (val?.m_shared == null)
			{
				return PriceResult.Unavailable("Missing registered item definition.");
			}
			string text = GameAdapter.PrefabName(registered);
			PriceResult val2 = economy.Price(text, quality);
			if (!rarity.HasValue || !val2.Available || EpicSupplementValuation.IsFixedRelic(text))
			{
				return val2;
			}
			try
			{
				Type api = Ready();
				int num = Scalar<int>(api, "GetRarityCount");
				ItemData val3 = Detached(val);
				val3.m_quality = quality;
				val3.m_dropPrefab = registered;
				if (num != 6 || rarity.Value < 0 || rarity.Value >= num || !Query<bool>(api, "CanBeMagicItem", val3))
				{
					return PriceResult.Unavailable("Invalid rarity or an item that cannot be enchanted.");
				}
				IReadOnlyList<EpicLootCost> readOnlyList = EpicLootMetadata.ParseCosts(EnchantCosts(api, val3, rarity.Value));
				PriceResult val4 = EpicLootMetadata.EnchantPrice(economy, text, quality, readOnlyList);
				if (!val4.Available)
				{
					if (readOnlyList.Count != 0 && !readOnlyList.Any((EpicLootCost x) => !economy.BasePrice(x.Item, 1).Available))
					{
						return val4;
					}
					val4 = PriceResult.Priced(val2.UnitValue, "Base estimate only: " + val4.Reason);
				}
				return EpicGearValuation.Apply(val4, rarity.Value);
			}
			catch (Exception ex)
			{
				return PriceResult.Unavailable("Epic Loot enchant valuation unavailable: " + ex.GetBaseException().Message);
			}
		}

		public static bool CanBeWagered(ItemData item)
		{
			return item?.m_shared != null;
		}

		public static ItemData Detached(ItemData item)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			if (item?.m_shared == null)
			{
				throw new ArgumentException("Missing item data.");
			}
			ItemData val = (ItemData)ShallowCopy.Invoke(item, null);
			val.m_shared = (SharedData)ShallowCopy.Invoke(item.m_shared, null);
			val.m_customData = new Dictionary<string, string>(item.m_customData ?? new Dictionary<string, string>(), StringComparer.Ordinal);
			val.m_equipped = false;
			return val;
		}

		public static bool HasPotentiallyOrdinaryMetadata(ItemData item)
		{
			if (item?.m_shared != null)
			{
				return !EpicLootMetadata.HasUnreviewedCustomData((IEnumerable<KeyValuePair<string, string>>)(item.m_customData ?? new Dictionary<string, string>()), true);
			}
			return false;
		}

		public static IReadOnlyList<EpicLootEnchantOption> GetEnchantOptions(ItemData item, Economy economy)
		{
			List<EpicLootEnchantOption> list = new List<EpicLootEnchantOption>();
			if (!AssemblyPresent || item?.m_shared == null)
			{
				return list;
			}
			try
			{
				Type api = Ready();
				item = Detached(item);
				if (!Query<bool>(api, "CanBeMagicItem", item) || Query<bool>(api, "IsUnidentified", item))
				{
					return list;
				}
				int num = Scalar<int>(api, "GetRarityCount");
				if (num < 1 || num > 100)
				{
					throw new InvalidOperationException("Unsupported rarity count.");
				}
				for (int i = 0; i < num; i++)
				{
					string text = EnchantCosts(api, item, i);
					IReadOnlyList<EpicLootCost> readOnlyList = EpicLootMetadata.ParseCosts(text);
					PriceResult val = EpicGearValuation.Apply(EpicLootMetadata.EnchantPrice(economy, GameAdapter.PrefabName(item), item.m_quality, readOnlyList), i);
					list.Add(new EpicLootEnchantOption
					{
						Rarity = i,
						Display = Localize(RarityText(api, "GetRarityDisplayNameByIndex", i)),
						Color = RarityText(api, "GetRarityColorByIndex", i),
						CostsJson = text,
						CostSummary = EpicLootMetadata.DescribeCosts(readOnlyList),
						BlockReason = val.Reason,
						Available = false
					});
				}
			}
			catch (Exception ex)
			{
				list.Clear();
				list.Add(new EpicLootEnchantOption
				{
					Rarity = -1,
					Display = "Epic Loot unavailable",
					BlockReason = ex.GetBaseException().Message
				});
			}
			return list;
		}
	}
	public sealed class FateForgeStation : MonoBehaviour, Hoverable, Interactable
	{
		public const float InteractionRange = 4f;

		public string GetHoverName()
		{
			return "Fate Forge";
		}

		public string GetHoverText()
		{
			if (!SessionGuard.Allows(out var reason))
			{
				return "Fate Forge\n" + reason;
			}
			if (Localization.instance == null)
			{
				return "Fate Forge\n[E] Open";
			}
			return Localization.instance.Localize("Fate Forge\n[<color=yellow><b>$KEY_Use</b></color>] Open");
		}

		public float GetHoverOffset()
		{
			return 0f;
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			if (hold || (Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (!SessionGuard.Allows(out var reason))
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, reason, 0, (Sprite)null, false);
				return false;
			}
			if (!IsAccessible(Player.m_localPlayer))
			{
				return false;
			}
			return StationRegistration.Open(this);
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}

		public bool IsAccessible(Player player, float distance = 4f)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || !((Behaviour)this).isActiveAndEnabled || !((Component)this).gameObject.activeInHierarchy || float.IsNaN(distance) || float.IsInfinity(distance) || distance <= 0f)
			{
				return false;
			}
			ZNetView component = ((Component)this).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return false;
			}
			return Vector3.Distance(((Component)player).transform.position, ((Component)this).transform.position) <= Math.Min(distance, 6f);
		}

		public static FateForgeStation FindNearest(Player player, float distance = 4f)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			FateForgeStation result = null;
			float num = distance;
			FateForgeStation[] array = Object.FindObjectsByType<FateForgeStation>((FindObjectsSortMode)0);
			foreach (FateForgeStation fateForgeStation in array)
			{
				if (fateForgeStation.IsAccessible(player, distance))
				{
					float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)fateForgeStation).transform.position);
					if (num2 <= num)
					{
						num = num2;
						result = fateForgeStation;
					}
				}
			}
			return result;
		}
	}
	internal sealed class ForgeAssets
	{
		public Texture2D Atlas { get; }

		public Texture2D Emblem { get; }

		public Sprite Icon { get; }

		public ForgeAssets()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_0052: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FateForge.atlas.png"))
			{
				using MemoryStream memoryStream = new MemoryStream();
				if (stream == null)
				{
					throw new InvalidOperationException("Fate Forge texture atlas is missing.");
				}
				stream.CopyTo(memoryStream);
				Atlas = new Texture2D(2, 2, (TextureFormat)4, true)
				{
					name = "FateForge_OriginalAtlas",
					wrapMode = (TextureWrapMode)1
				};
				if (!ImageConversion.LoadImage(Atlas, memoryStream.ToArray(), false))
				{
					throw new InvalidDataException("Invalid Fate Forge texture atlas.");
				}
			}
			int num = ((Texture)Atlas).width / 2;
			int num2 = ((Texture)Atlas).height / 2;
			Emblem = new Texture2D(num, num2, (TextureFormat)4, true)
			{
				name = "FateForge_OriginalEmblem",
				wrapMode = (TextureWrapMode)1
			};
			Emblem.SetPixels(Atlas.GetPixels(num, 0, num, num2));
			Emblem.Apply(true, true);
			Icon = Sprite.Create(Emblem, new Rect(0f, 0f, (float)num, (float)num2), new Vector2(0.5f, 0.5f), 100f);
			((Object)Icon).name = "FateForge_HammerIcon";
			Object.DontDestroyOnLoad((Object)(object)Atlas);
			Object.DontDestroyOnLoad((Object)(object)Emblem);
			Object.DontDestroyOnLoad((Object)(object)Icon);
		}
	}
	internal sealed class Quote
	{
		public string Id = Guid.NewGuid().ToString("N");

		public ItemData Stake;

		public WagerSelection Wager;

		public Player Owner;

		public Inventory Inventory;

		public ZNet Network;

		public long WorldUid;

		public byte[] InventoryState;

		public string StakePrefab;

		public int StakeQuality;

		public int StakeQuantity;

		public int RewardQuality;

		public int RewardQuantity;

		public int? StakeRarity;

		public string RewardPrefab;

		public string RewardMetadataFingerprint;

		public decimal StakeUnitValue;

		public decimal RewardUnitValue;

		public decimal WagerValue;

		public decimal RewardValue;

		public decimal Probability;

		public float Created;

		public void RequireInventory(Inventory current)
		{
			if (Inventory == null || Inventory != current)
			{
				throw new InvalidOperationException("Player inventory changed. Review again.");
			}
		}

		public void RequireContext()
		{
			if (Owner != Player.m_localPlayer || Network != ZNet.instance || (Object)(object)Owner == (Object)null || (Object)(object)ZNet.instance == (Object)null || WorldUid != ZNet.instance.GetWorldUID())
			{
				throw new InvalidOperationException("Player or world changed. Review again.");
			}
			RequireInventory(((Humanoid)Owner).GetInventory());
			if (((Character)Owner).IsDead() || ((Character)Owner).IsTeleporting())
			{
				throw new InvalidOperationException("Character died or began teleporting. Inventory changes cannot continue or be refunded safely.");
			}
		}
	}
	[DataContract]
	internal sealed class AuditRecord
	{
		[DataMember]
		public string id;

		[DataMember]
		public string state;

		[DataMember]
		public string utc;

		[DataMember]
		public string stake;

		[DataMember]
		public string reward;

		[DataMember]
		public string probability;

		[DataMember]
		public string detail;

		[DataMember]
		public int stakeQuality;

		[DataMember]
		public int stakeQuantity;

		[DataMember]
		public int rewardQuality;

		[DataMember]
		public int rewardQuantity;

		public string Serialize()
		{
			using MemoryStream memoryStream = new MemoryStream();
			new DataContractJsonSerializer(typeof(AuditRecord)).WriteObject(memoryStream, this);
			return Encoding.UTF8.GetString(memoryStream.ToArray());
		}
	}
	internal sealed class GambleOutcome
	{
		public WheelRoll Roll;

		public string Message;
	}
	internal sealed class GambleSession
	{
		private readonly string _journal;

		private readonly TransactionCoordinator _transactions = new TransactionCoordinator((Func<double>)(() => Time.realtimeSinceStartup));

		private Quote _issued;

		public bool Quarantined => _transactions.Quarantined;

		public GambleSession(string journal)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			_journal = journal;
		}

		public void CancelQuote()
		{
			_issued = null;
		}

		internal void ValidateMultiplayer(Quote quote, Catalogue catalogue, Func<bool> accessible)
		{
			if (quote == null || _issued != quote)
			{
				throw new InvalidOperationException("The review was cancelled. Nothing was spent.");
			}
			quote.RequireContext();
			if (!accessible() || Time.realtimeSinceStartup - quote.Created > 30f)
			{
				throw new InvalidOperationException("Review expired or you left the station. Nothing was spent.");
			}
			Quote quote2 = CreateQuote(catalogue, quote.Stake, quote.StakeQuantity, quote.RewardPrefab, quote.RewardQuantity, quote.RewardQuality);
			if (quote2.StakePrefab != quote.StakePrefab || quote2.StakeQuality != quote.StakeQuality || quote2.StakeRarity != quote.StakeRarity || quote2.RewardMetadataFingerprint != quote.RewardMetadataFingerprint || quote2.StakeUnitValue != quote.StakeUnitValue || quote2.RewardUnitValue != quote.RewardUnitValue || quote2.WagerValue != quote.WagerValue || quote2.RewardValue != quote.RewardValue || quote2.Probability != quote.Probability || !quote2.InventoryState.SequenceEqual(quote.InventoryState))
			{
				throw new InvalidOperationException("Inventory or prices changed. Review again; nothing was spent.");
			}
			_issued = null;
		}

		public Quote Quote(Catalogue catalogue, ItemData stake, int stakeCount, string rewardPrefab, int rewardCount, int rewardQuality)
		{
			_issued = null;
			return _issued = CreateQuote(catalogue, stake, stakeCount, rewardPrefab, rewardCount, rewardQuality);
		}

		private Quote CreateQuote(Catalogue catalogue, ItemData stake, int stakeCount, string rewardPrefab, int rewardCount, int rewardQuality)
		{
			if (!SessionGuard.Allows(out var reason))
			{
				throw new InvalidOperationException(reason);
			}
			if (Quarantined)
			{
				throw new InvalidOperationException("Gambling is locked for this session.");
			}
			if (!GameAdapter.RuntimeStillCurrent(catalogue))
			{
				throw new InvalidOperationException("Loaded mods, registered items, or recipes changed. Reopen Fate Forge.");
			}
			Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory();
			if (stake == null || !inventory.GetAllItems().Contains(stake) || stake.m_equipped)
			{
				throw new InvalidOperationException("Select an unequipped item still in your inventory.");
			}
			WagerSelection wager = WagerSelection.Create(inventory, stake, stakeCount);
			if (!catalogue.Items.TryGetValue(rewardPrefab ?? "", out var value))
			{
				throw new InvalidOperationException("Select a valid reward.");
			}
			if (!GameAdapter.IsLiveItem(catalogue, value))
			{
				throw new InvalidOperationException("The reward is no longer present in the loaded game. Reopen Fate Forge.");
			}
			if (rewardQuality < 1 || rewardQuality > value.MaxQuality || rewardCount < 1 || rewardCount > value.MaxStack)
			{
				throw new InvalidOperationException("Invalid reward quantity/quality. This prototype awards at most one inventory stack.");
			}
			string text = GameAdapter.PrefabName(stake);
			if (!catalogue.Items.TryGetValue(text, out var value2) || !GameAdapter.IsLiveItem(catalogue, value2))
			{
				throw new InvalidOperationException("The offering is no longer registered in the loaded game. Reopen Fate Forge.");
			}
			int? wagerRarity;
			PriceResult val = EpicLootBridge.Price(catalogue.Economy, stake, out wagerRarity);
			PriceResult val2 = EpicLootBridge.PriceCanonicalReward(catalogue.Economy, value.Object, rewardQuality);
			string text2 = RewardMetadataIdentity.Capture(value.Drop.m_itemData);
			EpicLootInspection epicLootInspection = EpicLootBridge.Inspect(value.Drop.m_itemData);
			if (!epicLootInspection.Available)
			{
				throw new InvalidOperationException("Reward unavailable: " + epicLootInspection.Reason);
			}
			RewardMetadataIdentity.RequireCurrent(value.Drop.m_itemData, text2);
			ItemData val3 = EpicLootBridge.Detached(value.Drop.m_itemData);
			StackLimits.PrepareReward(val3, value.Drop.m_itemData);
			RewardMetadataIdentity.RequireCurrent(val3, text2);
			val3.m_dropPrefab = value.Object;
			val3.m_quality = rewardQuality;
			val3.m_stack = rewardCount;
			val3.m_equipped = false;
			val3.m_durability = val3.GetMaxDurability();
			new RewardSafety(inventory, val3);
			if (!val.Available)
			{
				throw new InvalidOperationException("Wager is unpriced: " + val.Reason);
			}
			if (!val2.Available)
			{
				throw new InvalidOperationException("Reward is unpriced: " + val2.Reason);
			}
			decimal num = Odds.Total(val.UnitValue, stakeCount);
			decimal num2 = Odds.Total(val2.UnitValue, rewardCount);
			decimal num3 = Odds.Probability(num, num2);
			if (Odds.WinningDrawCount(num3) == 0L)
			{
				throw new InvalidOperationException("This wager is below the supported probability resolution. Increase it.");
			}
			return new Quote
			{
				Owner = Player.m_localPlayer,
				Inventory = inventory,
				Network = ZNet.instance,
				WorldUid = ZNet.instance.GetWorldUID(),
				InventoryState = InventorySnapshot.Serialize(inventory),
				Stake = stake,
				Wager = wager,
				StakePrefab = text,
				StakeQuality = stake.m_quality,
				StakeQuantity = stakeCount,
				StakeRarity = wagerRarity,
				RewardPrefab = rewardPrefab,
				RewardQuantity = rewardCount,
				RewardMetadataFingerprint = text2,
				RewardQuality = rewardQuality,
				StakeUnitValue = val.UnitValue,
				RewardUnitValue = val2.UnitValue,
				WagerValue = num,
				RewardValue = num2,
				Probability = num3,
				Created = Time.realtimeSinceStartup
			};
		}

		public GambleOutcome Execute(Catalogue catalogue, Quote quote, Func<bool> stationStillAccessible)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZNet.IsOpenServer() || ZNet.instance.GetPeers().Count != 0)
			{
				throw new InvalidOperationException("Multiplayer gambling requires a built Fate Forge.");
			}
			if (quote == null || _issued != quote)
			{
				throw new InvalidOperationException("Review a current wager first.");
			}
			_issued = null;
			InventorySnapshot backup = null;
			Inventory inventory = null;
			RewardSafety deliverySafety = null;
			WheelRoll roll = null;
			try
			{
				TransactionResult val = _transactions.Execute(quote.Id, (double)quote.Created, (Func<bool>)delegate
				{
					quote.RequireContext();
					if (!stationStillAccessible())
					{
						throw new InvalidOperationException("You left the crafting station or closed the panel.");
					}
					Quote quote2 = CreateQuote(catalogue, quote.Stake, quote.StakeQuantity, quote.RewardPrefab, quote.RewardQuantity, quote.RewardQuality);
					quote.RequireContext();
					quote.RequireInventory(quote2.Inventory);
					if (quote2.StakePrefab != quote.StakePrefab || quote2.StakeQuality != quote.StakeQuality || quote2.StakeRarity != quote.StakeRarity || quote2.RewardMetadataFingerprint != quote.RewardMetadataFingerprint || quote2.WagerValue != quote.WagerValue || quote2.RewardValue != quote.RewardValue || quote2.Probability != quote.Probability || !quote2.InventoryState.SequenceEqual(quote.InventoryState))
					{
						throw new InvalidOperationException("Inventory, items, or prices changed. Review again.");
					}
					inventory = quote.Inventory;
					return true;
				}, (Func<object>)(() => backup = new InventorySnapshot(inventory, quote.Owner)), (Action)delegate
				{
					Append(quote, "prepared", "Local synchronous test; not crash-recoverable.");
				}, (Func<bool>)delegate
				{
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_001b: Expected O, but got Unknown
					roll = new WheelRoll(quote.Probability, Odds.Draw());
					return roll.Won;
				}, (Action)delegate
				{
					quote.RequireContext();
					quote.Wager.Consume(inventory, quote.RequireContext);
					quote.RequireContext();
					backup.VerifyConsumed(quote.Wager);
				}, (Action)delegate
				{
					quote.RequireContext();
					CatalogueEntry catalogueEntry = catalogue.Items[quote.RewardPrefab];
					if (!GameAdapter.RuntimeStillCurrent(catalogue) || !GameAdapter.IsLiveItem(catalogue, catalogueEntry))
					{
						throw new InvalidOperationException("The loaded reward prefab changed during delivery.");
					}
					RewardMetadataIdentity.RequireCurrent(catalogueEntry.Drop.m_itemData, quote.RewardMetadataFingerprint);
					ItemData val2 = EpicLootBridge.Detached(catalogueEntry.Drop.m_itemData);
					StackLimits.PrepareReward(val2, catalogueEntry.Drop.m_itemData);
					RewardMetadataIdentity.RequireCurrent(val2, quote.RewardMetadataFingerprint);
					val2.m_dropPrefab = catalogueEntry.Object;
					val2.m_quality = quote.RewardQuality;
					val2.m_stack = quote.RewardQuantity;
					val2.m_equipped = false;
					val2.m_durability = val2.GetMaxDurability();
					RewardMetadataIdentity.RequireCurrent(val2, quote.RewardMetadataFingerprint);
					deliverySafety = new RewardSafety(inventory, val2);
					if (!deliverySafety.Deliver(quote.RequireContext))
					{
						throw new InvalidOperationException("The reward could not be added in full.");
					}
					quote.RequireContext();
				}, (Action<bool>)delegate(bool won)
				{
					quote.RequireContext();
					backup.VerifyResult(quote.Wager, quote.RewardPrefab, quote.RewardQuality, quote.RewardQuantity, won);
					if (won)
					{
						deliverySafety.VerifyDelivered();
					}
				}, (Action<object>)delegate(object state)
				{
					quote.RequireContext();
					((InventorySnapshot)state).Restore();
					quote.RequireContext();
				}, (Action<bool>)delegate(bool won)
				{
					Append(quote, won ? "won" : "lost", "Exact inventory mutation verified; normal game saving still applies.");
				});
				string text = ((val.JournalWarning == "") ? "" : (" Result committed; journal failed: " + val.JournalWarning));
				return new GambleOutcome
				{
					Roll = roll,
					Message = (val.Won ? "WIN: exact reward received. Your wager was consumed." : "LOSS: your wager was consumed; no reward.") + text
				};
			}
			catch (Exception ex)
			{
				try
				{
					Append(quote, Quarantined ? "quarantined" : "aborted", ex.Message);
				}
				catch
				{
				}
				throw;
			}
		}

		private void Append(Quote quote, string state, string detail)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(_journal));
			AuditRecord auditRecord = new AuditRecord
			{
				id = quote.Id,
				state = state,
				utc = DateTime.UtcNow.ToString("O"),
				stake = quote.StakePrefab,
				stakeQuality = quote.StakeQuality,
				stakeQuantity = quote.StakeQuantity,
				reward = quote.RewardPrefab,
				rewardQuality = quote.RewardQuality,
				rewardQuantity = quote.RewardQuantity,
				probability = quote.Probability.ToString(CultureInfo.InvariantCulture),
				detail = detail
			};
			File.AppendAllText(_journal, auditRecord.Serialize() + Environment.NewLine);
		}
	}
	internal sealed class GambleWheel : IDisposable
	{
		private const int TextureSize = 512;

		private const int NeedleTextureSize = 64;

		private const float MarkerOrbit = 0.414f;

		private const float MarkerSize = 0.18f;

		private static readonly Color WinColor = new Color(0.2f, 0.73f, 0.4f, 1f);

		private static readonly Color LossColor = new Color(0.6f, 0.18f, 0.13f, 1f);

		private static readonly Color GoldColor = new Color(0.85f, 0.69f, 0.38f, 1f);

		private Texture2D _ring;

		private Texture2D _needle;

		private GUIStyle _percentStyle;

		private GUIStyle _smallStyle;

		private GUIStyle _outcomeStyle;

		private decimal _textureProbability = -1m;

		private decimal _needleTurn = -1m;

		private readonly Color32[] _needlePixels = (Color32[])(object)new Color32[4096];

		private double _started;

		private bool _disposed;

		private Texture2D _faceTexture;

		public WheelRoll Result { get; private set; }

		public bool HasResult => Result != null;

		public bool IsSpinning
		{
			get
			{
				if (HasResult)
				{
					return !WheelPresentation.Finished(Elapsed);
				}
				return false;
			}
		}

		public Texture2D FaceTexture
		{
			get
			{
				return _faceTexture;
			}
			set
			{
				if ((Object)(object)_faceTexture != (Object)(object)value)
				{
					_faceTexture = value;
					_textureProbability = -1m;
				}
			}
		}

		private double Elapsed => Math.Max(0.0, Time.realtimeSinceStartupAsDouble - _started);

		public void Start(WheelRoll committedRoll)
		{
			Start(committedRoll, Time.realtimeSinceStartupAsDouble);
		}

		public void Start(WheelRoll committedRoll, double startedAt)
		{
			if (_disposed)
			{
				throw new ObjectDisposedException("GambleWheel");
			}
			if (double.IsNaN(startedAt) || double.IsInfinity(startedAt) || startedAt < 0.0)
			{
				throw new ArgumentOutOfRangeException("startedAt");
			}
			Result = committedRoll ?? throw new ArgumentNullException("committedRoll");
			_started = startedAt;
		}

		public void Clear()
		{
			Result = null;
		}

		public void Draw(Rect bounds, decimal rawPreviewProbability)
		{
			//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Expected O, but got Unknown
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0266: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0322: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dd: Unknown result type (might be due to invalid IL or missing references)
			if (_disposed || ((Rect)(ref bounds)).width < 40f || ((Rect)(ref bounds)).height < 70f)
			{
				return;
			}
			decimal num = (HasResult ? Result.EffectiveProbability : Odds.EffectiveProbability(rawPreviewProbability));
			EnsureResources(num);
			EnsureStyles();
			float num2 = Math.Min(((Rect)(ref bounds)).width, ((Rect)(ref bounds)).height - 76f);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((Rect)(ref bounds)).x + (((Rect)(ref bounds)).width - num2) / 2f, ((Rect)(ref bounds)).y, num2, num2);
			Vector2 center = ((Rect)(ref val)).center;
			bool isSpinning = IsSpinning;
			Color color = GUI.color;
			try
			{
				GUI.color = Color.white;
				if ((Object)(object)FaceTexture != (Object)null)
				{
					GUI.DrawTexture(new Rect(center.x - num2 * 0.34f, center.y - num2 * 0.34f, num2 * 0.68f, num2 * 0.68f), (Texture)(object)FaceTexture, (ScaleMode)2, true);
				}
				GUI.DrawTexture(val, (Texture)(object)_ring, (ScaleMode)2, true);
				decimal turns = (HasResult ? WheelPresentation.RotationTurns(Result, Elapsed) : 0m);
				EnsureNeedle(turns);
				GUI.DrawTexture(MarkerRect(val, turns), (Texture)(object)_needle, (ScaleMode)0, true);
				_percentStyle.fontSize = Math.Max(16, (int)(num2 * 0.112f));
				_percentStyle.normal.textColor = GoldColor;
				GUI.Label(new Rect(center.x - num2 * 0.33f, center.y - num2 * 0.17f, num2 * 0.66f, num2 * 0.25f), new GUIContent(CompactPercent(num), "Win chance: " + CompactPercent(num)), _percentStyle);
				_smallStyle.normal.textColor = new Color(0.83f, 0.81f, 0.72f);
				GUI.Label(new Rect(center.x - num2 * 0.3f, center.y + num2 * 0.028f, num2 * 0.6f, 21f), "WIN CHANCE", _smallStyle);
				_outcomeStyle.normal.textColor = (Color)((!HasResult || isSpinning) ? GoldColor : (Result.Won ? WinColor : new Color(0.96f, 0.46f, 0.32f)));
				string text = ((!HasResult) ? "THE FATES AWAIT" : (isSpinning ? "THE FATES TURN..." : (Result.Won ? "WIN" : "LOSS - WAGER SPENT")));
				GUI.Label(new Rect(((Rect)(ref bounds)).x, ((Rect)(ref val)).yMax + 1f, ((Rect)(ref bounds)).width, 23f), text, _outcomeStyle);
				_smallStyle.normal.textColor = new Color(0.79f, 0.76f, 0.66f);
				GUI.Label(new Rect(((Rect)(ref bounds)).x, ((Rect)(ref val)).yMax + 26f, ((Rect)(ref bounds)).width, 20f), "GREEN: WIN   /   RED: LOSS", _smallStyle);
				string text2 = ((HasResult && !isSpinning && WheelPresentation.NeedsPixelPrecisionNote(Result, (double)num2 * 0.43)) ? "Arc/boundary below pixel size. Exact result above." : (HasResult ? "Wager settled. Closing cannot change the result." : ((num == 0m) ? "Review a valid wager to see its chance." : (((double)num * (Math.PI * 2.0 * (double)num2 * 0.43) < 1.0) ? "The winning area is too small to see." : "Green area follows your exact chance."))));
				GUI.Label(new Rect(((Rect)(ref bounds)).x, ((Rect)(ref val)).yMax + 47f, ((Rect)(ref bounds)).width, 29f), text2, _smallStyle);
			}
			finally
			{
				GUI.color = color;
			}
		}

		internal static string CompactPercent(decimal probability)
		{
			if (probability < 0m || probability > 0.95m)
			{
				throw new ArgumentOutOfRangeException("probability");
			}
			decimal num = probability * 100m;
			string text = ((num >= 0.01m) ? "0.##" : ((num >= 0.000001m || num == 0m) ? "0.######" : "0.###E+0"));
			return num.ToString(text, CultureInfo.InvariantCulture) + "%";
		}

		internal static Rect MarkerRect(Rect ring, decimal turns)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (turns < 0m || !Finite(((Rect)(ref ring)).x) || !Finite(((Rect)(ref ring)).y) || !Finite(((Rect)(ref ring)).width) || !Finite(((Rect)(ref ring)).height) || ((Rect)(ref ring)).width <= 0f || ((Rect)(ref ring)).height <= 0f || Math.Abs(((Rect)(ref ring)).width - ((Rect)(ref ring)).height) > 0.001f)
			{
				throw new ArgumentOutOfRangeException("ring", "Marker geometry requires a finite square ring and nonnegative phase.");
			}
			double num = (double)(turns % 1m) * Math.PI * 2.0;
			float num2 = ((Rect)(ref ring)).width * 0.18f;
			Vector2 val = ((Rect)(ref ring)).center + new Vector2((float)Math.Sin(num), 0f - (float)Math.Cos(num)) * (((Rect)(ref ring)).width * 0.414f);
			return new Rect(val.x - num2 / 2f, val.y - num2 / 2f, num2, num2);
		}

		internal static Vector2[] MarkerVertices(decimal turns)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			if (turns < 0m)
			{
				throw new ArgumentOutOfRangeException("turns");
			}
			double num = (double)(turns % 1m) * Math.PI * 2.0;
			float num2 = (float)Math.Cos(num);
			float num3 = (float)Math.Sin(num);
			Vector2[] array = (Vector2[])(object)new Vector2[7]
			{
				new Vector2(0f, 0.06f),
				new Vector2(-0.042f, -0.02f),
				new Vector2(-0.014f, -0.02f),
				new Vector2(-0.014f, -0.077f),
				new Vector2(0.014f, -0.077f),
				new Vector2(0.014f, -0.02f),
				new Vector2(0.042f, -0.02f)
			};
			for (int i = 0; i < array.Length; i++)
			{
				Vector2 val = array[i];
				array[i] = new Vector2(0.5f + (val.x * num2 - val.y * num3) / 0.18f, 0.5f + (val.x * num3 + val.y * num2) / 0.18f);
			}
			return array;
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			if (_percentStyle == null)
			{
				_percentStyle = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)4,
					fontStyle = (FontStyle)1
				};
				_smallStyle = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)4,
					fontSize = 11,
					wordWrap = true
				};
				_outcomeStyle = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)4,
					fontSize = 14,
					fontStyle = (FontStyle)1
				};
			}
		}

		private void EnsureResources(decimal probability)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0393: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_ring == (Object)null)
			{
				_ring = new Texture2D(512, 512, (TextureFormat)4, false)
				{
					name = "FateForge_ChanceRing",
					hideFlags = (HideFlags)61,
					filterMode = (FilterMode)1,
					wrapMode = (TextureWrapMode)1
				};
			}
			if (_textureProbability == probability)
			{
				return;
			}
			Color32[] array = (Color32[])(object)new Color32[262144];
			for (int i = 0; i < 512; i++)
			{
				for (int j = 0; j < 512; j++)
				{
					double num = ((double)j + 0.5 - 256.0) / 256.0;
					double num2 = ((double)i + 0.5 - 256.0) / 256.0;
					double num3 = Math.Sqrt(num * num + num2 * num2);
					double num4 = Math.Atan2(num, num2) / (Math.PI * 2.0);
					if (num4 < 0.0)
					{
						num4 += 1.0;
					}
					Color val = Color.clear;
					if (num3 < 0.974)
					{
						float num5 = (float)(0.026 * Math.Sin(num * 94.0 + Math.Sin(num2 * 31.0) * 3.0) + 0.018 * Math.Sin(num2 * 180.0));
						((Color)(ref val))..ctor(0.15f + num5, 0.109f + num5 * 0.7f, 0.067f + num5 * 0.4f, 0.98f);
						if (num3 < 0.64)
						{
							((Color)(ref val))..ctor(0.041f, 0.052f, 0.046f, ((Object)(object)FaceTexture != (Object)null) ? 0.81f : 0.98f);
						}
						else if (num3 > 0.7 && num3 < 0.925)
						{
							double num6 = 1.0 / (512.0 * num3 * 2.0 * Math.PI);
							float num7 = (float)WheelPresentation.GreenCoverage(probability, num4, num6);
							val = Color.Lerp(LossColor, WinColor, num7);
							float num8 = (float)(0.78 + 0.22 * Math.Sin((num3 - 0.7) / 0.225 * Math.PI));
							val *= num8;
							val.a = 1f;
						}
						else if ((num3 > 0.654 && num3 < 0.68) || num3 > 0.94)
						{
							val = GoldColor * (float)(0.67 + 0.2 * Math.Cos(num4 * Math.PI * 32.0));
						}
						if (num3 > 0.935 && Math.Abs(num4 * 32.0 - Math.Round(num4 * 32.0)) < 0.06)
						{
							((Color)(ref val))..ctor(0.12f, 0.095f, 0.05f, 1f);
						}
						val.a *= (float)Math.Min(1.0, (0.974 - num3) * 512.0 / 2.0);
					}
					array[i * 512 + j] = Color32.op_Implicit(val);
				}
			}
			_ring.SetPixels32(array);
			_ring.Apply(false, false);
			_textureProbability = probability;
		}

		private void EnsureNeedle(decimal turns)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			decimal num = turns % 1m;
			if ((Object)(object)_needle == (Object)null)
			{
				_needle = new Texture2D(64, 64, (TextureFormat)4, false)
				{
					name = "FateForge_RuneNeedle",
					hideFlags = (HideFlags)61,
					filterMode = (FilterMode)1,
					wrapMode = (TextureWrapMode)1
				};
				_needleTurn = -1m;
			}
			if (_needleTurn == num)
			{
				return;
			}
			Vector2[] array = MarkerVertices(num);
			Vector2 val = default(Vector2);
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					((Vector2)(ref val))..ctor(((float)j + 0.5f) / 64f, 1f - ((float)i + 0.5f) / 64f);
					bool flag = false;
					float num2 = float.MaxValue;
					int num3 = 0;
					int num4 = array.Length - 1;
					while (num3 < array.Length)
					{
						Vector2 val2 = array[num4];
						Vector2 val3 = array[num3];
						if (val2.y > val.y != val3.y > val.y && val.x < (val3.x - val2.x) * (val.y - val2.y) / (val3.y - val2.y) + val2.x)
						{
							flag = !flag;
						}
						Vector2 val4 = val3 - val2;
						float num5 = Math.Max(0f, Math.Min(1f, Vector2.Dot(val - val2, val4) / ((Vector2)(ref val4)).sqrMagnitude));
						float val5 = num2;
						Vector2 val6 = val - (val2 + val4 * num5);
						num2 = Math.Min(val5, ((Vector2)(ref val6)).sqrMagnitude);
						num4 = num3++;
					}
					_needlePixels[i * 64 + j] = Color32.op_Implicit((Color)((!flag) ? Color.clear : ((num2 < 0.00065f) ? new Color(0.2f, 0.12f, 0.04f, 1f) : new Color(1f, 0.88f, 0.57f, 1f))));
				}
			}
			_needle.SetPixels32(_needlePixels);
			_needle.Apply(false, false);
			_needleTurn = num;
		}

		public void Dispose()
		{
			if ((Object)(object)_ring != (Object)null)
			{
				Object.Destroy((Object)(object)_ring);
			}
			if ((Object)(object)_needle != (Object)null)
			{
				Object.Destroy((Object)(object)_needle);
			}
			_ring = null;
			_needle = null;
			Result = null;
			_disposed = true;
		}
	}
	[Serializable]
	public sealed class AnchorFile
	{
		public int schema;

		public string profile;

		public AnchorRow[] anchors;
	}
	[Serializable]
	public sealed class AnchorRow
	{
		public string prefab;

		public string value;

		public bool enabled;

		public string note;
	}
	public sealed class CatalogueEntry
	{
		public string Prefab;

		public string Display;

		public GameObject Object;

		public ItemDrop Drop;

		public int MaxQuality;

		public int MaxStack;

		public bool ShowInBrowser;

		public bool IsInventoryItem;
	}
	public sealed class Catalogue
	{
		public Economy Economy;

		public ObjectDB Database;

		public GameObject[] RuntimeObjects;

		public string PluginSignature;

		public string StackConfigurationSignature;

		public string ProcessorSignature;

		public RuntimeRecipeSnapshot RecipeSnapshot;

		public NativeOriginSnapshot SourceSnapshot;

		public IReadOnlyDictionary<string, BiomeAdjustment> RouteBiomes;

		public RuntimeEpicLootValuationSnapshot EpicMaterialSnapshot;

		public bool EpicApiInstalled;

		public Dictionary<string, CatalogueEntry> Items;

		public List<string> Warnings = new List<string>();
	}
	internal static class GameFields
	{
		private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static FieldInfo Field(Type type, string name)
		{
			while (type != null)
			{
				FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field;
				}
				type = type.BaseType;
			}
			return null;
		}

		public static T Required<T>(object instance, string name)
		{
			FieldInfo fieldInfo = Field(instance.GetType(), name);
			if (fieldInfo == null)
			{
				throw new MissingFieldException(instance.GetType().FullName, name);
			}
			return (T)fieldInfo.GetValue(instance);
		}

		public static bool OptionalBool(object instance, string name)
		{
			object obj = Field(instance.GetType(), name)?.GetValue(instance);
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		public static object InvokeZero(object instance, string name)
		{
			MethodInfo? method = instance.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null)
			{
				throw new MissingMethodException(instance.GetType().FullName, name);
			}
			return method.Invoke(method.IsStatic ? null : instance, null);
		}
	}
	internal static class SessionGuard
	{
		public static bool Allows(out string reason)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Invalid comparison between Unknown and I4
			reason = "Enter a world with a living player to use Fate Forge.";
			if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return false;
			}
			try
			{
				ZNet instance = ZNet.instance;
				if ((bool)GameFields.InvokeZero(instance, "IsDedicated"))
				{
					return false;
				}
				if (!instance.IsServer() && (int)ZNet.GetConnectionStatus() != 2)
				{
					return false;
				}
				if (((Character)Player.m_localPlayer).IsDead())
				{
					reason = "Cannot gamble while dead.";
					return false;
				}
				if (((Character)Player.m_localPlayer).IsTeleporting())
				{
					reason = "Wait until teleporting finishes before gambling.";
					return false;
				}
				reason = "";
				return true;
			}
			catch (Exception ex)
			{
				reason = "Session API needs verification; gambling blocked: " + ex.GetBaseException().Message;
				return false;
			}
		}
	}
	internal static class GameAdapter
	{
		private static readonly KeyValuePair<string, string>[] ReviewedProcessorOutputs = new KeyValuePair<string, string>[15]
		{
			new KeyValuePair<string, string>("charcoal_kiln", "Coal"),
			new KeyValuePair<string, string>("smelter", "Copper"),
			new KeyValuePair<string, string>("smelter", "Tin"),
			new KeyValuePair<string, string>("smelter", "Iron"),
			new KeyValuePair<string, string>("smelter", "Silver"),
			new KeyValuePair<string, string>("smelter", "Bronze"),
			new KeyValuePair<string, string>("blastfurnace", "BlackMetal"),
			new KeyValuePair<string, string>("blastfurnace", "FlametalNew"),
			new KeyValuePair<string, string>("blastfurnace", "Gold"),
			new KeyValuePair<string, string>("eitrrefinery", "Eitr"),
			new KeyValuePair<string, string>("piece_spinningwheel", "LinenThread"),
			new KeyValuePair<string, string>("windmill", "BarleyFlour"),
			new KeyValuePair<string, string>("windmill", "Oat"),
			new KeyValuePair<string, string>("windmill", "OatFlour"),
			new KeyValuePair<string, string>("piece_FrostKiln", "FrozenFuel")
		};

		public static string PrefabName(GameObject obj)
		{
			if (!((Object)(object)obj == (Object)null))
			{
				return ((Object)obj).name.Replace("(Clone)", "").Trim();
			}
			return "";
		}

		public static string PrefabName(ItemData item)
		{
			if (item != null)
			{
				return PrefabName(item.m_dropPrefab);
			}
			return "";
		}

		public static string Name(ItemData item)
		{
			if (Localization.instance != null)
			{
				return Localization.instance.Localize(item.m_shared.m_name);
			}
			return item.m_shared.m_name;
		}

		public static string CatalogueName(ItemData item)
		{
			return CataloguePolicy.DisplayName(PrefabName(item), Name(item));
		}

		public static Catalogue Build(string configPath)
		{
			//IL_05cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d6: Expected O, but got Unknown
			//IL_0599: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a3: Expected O, but got Unknown
			//IL_054e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0558: Expected O, but got Unknown
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				throw new InvalidOperationException("The world item database is not ready.");
			}
			EpicLootBridge.EnsureCompatibleIfPresent();
			EconomyConfiguration val = EconomyConfiguration.Parse(File.ReadAllText(configPath));
			IReadOnlyDictionary<string, decimal> anchors = val.ActiveAnchors;
			IReadOnlyList<string> unreviewedEnabled = val.UnreviewedEnabled;
			Catalogue catalogue = new Catalogue
			{
				Database = ObjectDB.instance,
				EpicApiInstalled = EpicLootBridge.IsInstalled,
				RuntimeObjects = GameFields.Required<List<GameObject>>(ObjectDB.instance, "m_items").ToArray(),
				PluginSignature = InstalledModCatalogue.CurrentPluginSignature(),
				StackConfigurationSignature = StackLimits.ConfigurationSignature,
				Items = new Dictionary<string, CatalogueEntry>(StringComparer.Ordinal)
			};
			Dictionary<string, string> blocks = new Dictionary<string, string>(StringComparer.Ordinal);
			if (val.LegacyDefaultsSupplemented)
			{
				catalogue.Warnings.Add("Pristine older default uses the current progression profile and biome adjustments; Wood remains 0.3. Your configuration file is unchanged.");
			}
			foreach (string item in unreviewedEnabled)
			{
				catalogue.Warnings.Add("Ignored draft anchor; using live acquisition costs or difficulty estimate: " + item);
			}
			foreach (GameObject item2 in GameFields.Required<List<GameObject>>(ObjectDB.instance, "m_items"))
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				ItemDrop component = item2.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null)
				{
					continue;
				}
				SharedData shared = component.m_itemData.m_shared;
				string text = PrefabName(item2);
				if (!string.IsNullOrWhiteSpace(text))
				{
					if (catalogue.Items.ContainsKey(text))
					{
						throw new InvalidDataException("Duplicate runtime prefab: " + text);
					}
					bool flag = shared.m_maxQuality >= 1 && shared.m_maxQuality <= 100 && shared.m_maxStackSize >= 1 && !string.IsNullOrWhiteSpace(shared.m_name) && ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() != "None" && ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() != "Customization";
					if (!flag)
					{
						blocks[text] = "Not a supported obtainable inventory item.";
					}
					string text2 = (string.IsNullOrWhiteSpace(shared.m_name) ? text : CataloguePolicy.DisplayName(text, Name(component.m_itemData)));
					catalogue.Items.Add(text, new CatalogueEntry
					{
						Prefab = text,
						Display = (string.IsNullOrWhiteSpace(text2) ? text : text2),
						Object = item2,
						Drop = component,
						MaxQuality = Math.Max(1, Math.Min(100, shared.m_maxQuality)),
						MaxStack = (flag ? StackLimits.ForTemplate(component.m_itemData) : Math.Max(1, shared.m_maxStackSize)),
						IsInventoryItem = flag,
						ShowInBrowser = (flag && !CataloguePolicy.IsExcluded(text) && shared.m_icons != null && shared.m_icons.Any((Sprite icon) => (Object)(object)icon != (Object)null))
					});
				}
			}
			foreach (string key2 in anchors.Keys)
			{
				if (!catalogue.Items.ContainsKey(key2))
				{
					catalogue.Warnings.Add("Enabled anchor not present in this installation: " + key2);
				}
			}
			List<Recipe> list = GameFields.Required<List<Recipe>>(ObjectDB.instance, "m_recipes");
			catalogue.RecipeSnapshot = new RuntimeRecipeSnapshot(list);
			Dictionary<string, List<CraftRoute>> routes = new Dictionary<string, List<CraftRoute>>(StringComparer.Ordinal);
			foreach (Recipe item3 in list)
			{
				if ((Object)(object)item3 == (Object)null || !GameFields.Required<bool>(item3, "m_enabled"))
				{
					continue;
				}
				ItemDrop val2 = GameFields.Required<ItemDrop>(item3, "m_item");
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				string key = PrefabName(((Component)val2).gameObject);
				if (!catalogue.Items.TryGetValue(key, out var value))
				{
					continue;
				}
				if (value.IsInventoryItem)
				{
					value.ShowInBrowser = !CataloguePolicy.IsExcluded(value.Prefab);
				}
				if (!routes.TryGetValue(key, out var value2))
				{
					value2 = (routes[key] = new List<CraftRoute>());
				}
				try
				{
					ValidateRecipeIdentities(catalogue, item3);
					int num = GameFields.Required<int>(item3, "m_amount");
					string text3 = null;
					if (GameFields.Required<bool>(item3, "m_noCraftOnlyUpgrade"))
					{
						text3 = "Upgrade-only recipe needs an audited base acquisition route.";
					}
					if (GameFields.Required<bool>(item3, "m_requireOnlyOneIngredient"))
					{
						text3 = "Alternative-ingredient recipe needs a dedicated adapter.";
					}
					if (text3 == null)
					{
						num = OrdinaryOutputCount(item3);
					}
					Requirement[] array = GameFields.Required<Requirement[]>(item3, "m_resources");
					List<IReadOnlyList<Material>> list3 = new List<IReadOnlyList<Material>>();
					for (int num2 = 1; num2 <= value.MaxQuality; num2++)
					{
						ValidateOrdinaryCraftingStation(item3, num2);
						List<Material> list4 = new List<Material>();
						Requirement[] array2 = array;
						foreach (Requirement val3 in array2)
						{
							if (val3 == null)
							{
								continue;
							}
							int num4 = OrdinaryRequirementAmount(val3, num2);
							if (num4 != 0)
							{
								if ((Object)(object)val3.m_resItem == (Object)null)
								{
									throw new InvalidDataException("Ingredient prefab is missing.");
								}
								list4.Add(new Material(PrefabName(((Component)val3.m_resItem).gameObject), num4));
							}
						}
						list3.Add(list4);
					}
					value2.Add(new CraftRoute(((Object)item3).name, num, (IReadOnlyList<IReadOnlyList<Material>>)list3, text3, RecipeMarkupRate(val2)));
				}
				catch (Exception ex)
				{
					value2.Add(new CraftRoute(((Object)item3).name, 1, (IReadOnlyList<IReadOnlyList<Material>>)Array.Empty<IReadOnlyList<Material>>(), "Recipe import failed: " + ex.GetBaseException().Message));
				}
			}
			Smelter[] processors = RelevantProcessors().ToArray();
			ImportReviewedProcessors(catalogue, routes, blocks, processors);
			catalogue.ProcessorSignature = ReviewedProcessorSignature(processors);
			RuntimeDifficultyValuation.AddFermentationRoutes(catalogue, routes);
			RuntimeCookingValuation.AddConversionRoutes(catalogue, routes, val.CustomizedAnchors());
			RuntimeTraderValuation.AddConversionRoutes(catalogue, routes, val.CustomizedAnchors());
			RuntimeAlternativeConversionValuation.AddConversionRoutes(catalogue, routes, val.CustomizedAnchors());
			RuntimeEpicSupplementValuation.AddAccessoryRoutes(catalogue, routes);
			RuntimeEpicLootValuation.AddConversionRoutes(catalogue, routes, val.CustomizedAnchors());
			catalogue.SourceSnapshot = NativeOriginSnapshot.Capture();
			IReadOnlyDictionary<string, BiomeAdjustment> biomeAdjustments;
			IReadOnlyDictionary<string, DifficultyEstimate> estimates = RuntimeDifficultyValuation.Build(catalogue, anchors, out biomeAdjustments, routes);
			decimal value3;
			List<CraftRoute> value4;
			string value5;
			IEnumerable<ItemSpec> source = ((IEnumerable<CatalogueEntry>)catalogue.Items.Values).Select((Func<CatalogueEntry, ItemSpec>)((CatalogueEntry entry) => new ItemSpec(entry.Prefab, entry.MaxQuality, entry.MaxStack, anchors.TryGetValue(entry.Prefab, out value3) ? new decimal?(value3) : ((decimal?)null), (IReadOnlyList<CraftRoute>)(routes.TryGetValue(entry.Prefab, out value4) ? value4 : null), blocks.TryGetValue(entry.Prefab, out value5) ? value5 : null, TrophyValuation.IsTrophyType(((object)Unsafe.As<ItemType, ItemType>(ref entry.Drop.m_itemData.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString()), LimitConversionValue(entry))));
			HashSet<string> disabled = new HashSet<string>(val.DisabledReviewedAnchors, StringComparer.Ordinal);
			catalogue.Economy = BuildEconomyWithEstimates(source.Select((ItemSpec item) => (ItemSpec)(disabled.Contains(item.Prefab) ? ((object)new ItemSpec(item.Prefab, item.MaxQuality, item.MaxStack, item.Anchor, item.Routes, "Explicitly disabled in economy configuration.", item.IsTrophy, item.LimitConversionValue)) : ((object)item))), catalogue.Warnings, estimates, biomeAdjustments, TierMinimums(catalogue, biomeAdjustments, val.CustomizedAnchors()), catalogue.RouteBiomes);
			foreach (CatalogueEntry value6 in catalogue.Items.Values)
			{
				if (CataloguePolicy.IsExcluded(value6.Prefab))
				{
					value6.ShowInBrowser = false;
				}
			}
			foreach (IGrouping<string, CatalogueEntry> item4 in from x in catalogue.Items.Values.Where((CatalogueEntry x) => x.ShowInBrowser).GroupBy<CatalogueEntry, string>((CatalogueEntry x) => x.Display, StringComparer.OrdinalIgnoreCase)
				where x.Count() > 1
				select x)
			{
				foreach (CatalogueEntry item5 in item4)
				{
					item5.Display = item5.Display + " (" + item5.Prefab + ")";
				}
			}
			if (!catalogue.RecipeSnapshot.Matches(list))
			{
				throw new InvalidDataException("Recipe definitions changed while importing prices. Reopen Fate Forge.");
			}
			if (catalogue.StackConfigurationSignature != StackLimits.ConfigurationSignature)
			{
				throw new InvalidDataException("Item stack settings changed while loading the catalogue. Reopen Fate Forge.");
			}
			if (!catalogue.SourceSnapshot.Matches())
			{
				throw new InvalidDataException("Acquisition definitions changed while loading the catalogue. Reopen Fate Forge.");
			}
			return catalogue;
		}

		public static void ValidateRecipeIdentities(Catalogue catalogue, Recipe recipe)
		{
			if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null)
			{
				throw new InvalidDataException("Recipe output is missing.");
			}
			RequireLiveItemDefinition(catalogue, recipe.m_item);
			if (recipe.m_resources == null)
			{
				throw new InvalidDataException("Recipe ingredients are missing.");
			}
			int maxQuality = recipe.m_item.m_itemData.m_shared.m_maxQuality;
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement requirement in resources)
			{
				if (requirement != null && !requirement.m_upgraderResource && Enumerable.Range(1, Math.Max(1, Math.Min(100, maxQuality))).Any((int q) => OrdinaryRequirementAmount(requirement, q) != 0))
				{
					RequireLiveItemDefinition(catalogue, requirement.m_resItem);
				}
			}
		}

		public static bool IsDurableWeapon(CatalogueEntry entry)
		{
			SharedData shared = entry.Drop.m_itemData.m_shared;
			return DurableWeaponValuation.IsEligible(((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), shared.m_useDurability, Math.Max(0m, (decimal)((DamageTypes)(ref shared.m_damages)).GetTotalDamage()));
		}

		public static bool LimitConversionValue(CatalogueEntry entry)
		{
			if (IsDurableWeapon(entry))
			{
				return false;
			}
			SharedData shared = entry.Drop.m_itemData.m_shared;
			string text = ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString();
			if (shared.m_useDurability)
			{
				switch (text)
				{
				default:
					return !(text == "Shield");
				case "Helmet":
				case "Chest":
				case "Legs":
				case "Shoulder":
					return false;
				}
			}
			return true;
		}

		public static IReadOnlyDictionary<string, decimal> TierMinimums(Catalogue catalogue, IReadOnlyDictionary<string, BiomeAdjustment> biomes, IEnumerable<string> customAnchors = null)
		{
			return TierBaseValuation.CreateMinimums(biomes, (customAnchors ?? Array.Empty<string>()).Concat(new string[1] { "Andvaranaut" }), from x in catalogue.Items.Values.Where(IsDurableWeapon)
				select x.Prefab);
		}

		public static Economy BuildConfiguredEconomy(IEnumerable<ItemSpec> source, EconomyConfiguration configuration, ICollection<string> warnings)
		{
			HashSet<string> disabled = new HashSet<string>(configuration.DisabledReviewedAnchors, StringComparer.Ordinal);
			return BuildReviewedEconomy(source.Select((ItemSpec item) => (ItemSpec)(disabled.Contains(item.Prefab) ? ((object)new ItemSpec(item.Prefab, item.MaxQuality, item.MaxStack, item.Anchor, item.Routes, "Explicitly disabled in economy configuration.", item.IsTrophy, item.LimitConversionValue)) : ((object)item))), warnings);
		}

		public static Economy BuildReviewedEconomy(IEnumerable<ItemSpec> source, ICollection<string> warnings)
		{
			return BuildEconomyWithEstimates(source, warnings, null);
		}

		private static Economy BuildEconomyWithEstimates(IEnumerable<ItemSpec> source, ICollection<string> warnings, IReadOnlyDictionary<string, DifficultyEstimate> estimates, IReadOnlyDictionary<string, BiomeAdjustment> biomes = null, IReadOnlyDictionary<string, decimal> tierMinimums = null, IReadOnlyDictionary<string, BiomeAdjustment> routeBiomes = null)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Expected O, but got Unknown
			ItemSpec[] array = source.ToArray();
			Economy val = new Economy((IEnumerable<ItemSpec>)array, estimates, biomes, tierMinimums, routeBiomes);
			Dictionary<string, string> violations = new Dictionary<string, string>(StringComparer.Ordinal);
			KeyValuePair<string, decimal>[] array2 = new KeyValuePair<string, decimal>[1]
			{
				new KeyValuePair<string, decimal>("Wood", 0.3m)
			};
			for (int i = 0; i < array2.Length; i++)
			{
				KeyValuePair<string, decimal> keyValuePair = array2[i];
				PriceResult val2 = val.BasePrice(keyValuePair.Key, 1);
				if (val2.Available && !(val2.UnitValue == keyValuePair.Value))
				{
					string text = "Fixed base-cost conflict: imported " + keyValuePair.Key + " costs " + val2.UnitValue.ToString(CultureInfo.InvariantCulture) + "; required value is " + keyValuePair.Value.ToString(CultureInfo.InvariantCulture) + ". Review the changed acquisition costs.";
					violations.Add(keyValuePair.Key, text);
					warnings?.Add(text);
				}
			}
			string value;
			if (violations.Count != 0)
			{
				return new Economy(((IEnumerable<ItemSpec>)array).Select((Func<ItemSpec, ItemSpec>)((ItemSpec item) => new ItemSpec(item.Prefab, item.MaxQuality, item.MaxStack, item.Anchor, item.Routes, violations.TryGetValue(item.Prefab, out value) ? value : item.BlockReason, item.IsTrophy, item.LimitConversionValue))), estimates, biomes, tierMinimums, routeBiomes);
			}
			return val;
		}

		public static decimal RecipeMarkupRate(ItemDrop output)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)output == (Object)null || output.m_itemData?.m_shared == null)
			{
				throw new InvalidDataException("Recipe output metadata is missing.");
			}
			if (PrefabName(((Component)output).gameObject) == "SharpeningStone")
			{
				return 0.20m;
			}
			return RecipeMarkupRateForType(output.m_itemData.m_shared.m_itemType);
		}

		public static decimal RecipeMarkupRateForType(ItemType type)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Invalid comparison between Unknown and I4
			if (!Enum.IsDefined(typeof(ItemType), type) || (int)type == 0 || (int)type == 10)
			{
				throw new InvalidDataException("Recipe output type has no reviewed markup classification.");
			}
			if ((int)type == 1)
			{
				return 0.10m;
			}
			return 0.20m;
		}

		public static int OrdinaryOutputCount(Recipe recipe)
		{
			if (GameFields.Required<bool>(recipe, "m_noCraftOnlyUpgrade"))
			{
				throw new InvalidDataException("Upgrade-only recipe needs an audited base acquisition route.");
			}
			if (GameFields.Required<bool>(recipe, "m_requireOnlyOneIngredient"))
			{
				throw new InvalidDataException("Alternative-ingredient recipe needs a dedicated adapter.");
			}
			int num = GameFields.Required<int>(recipe, "m_amount");
			int num2 = default(int);
			ItemData val = default(ItemData);
			int amount = recipe.GetAmount(1, ref num2, ref val, 1);
			if (amount < 1 || amount != num || num2 != 0 || val != null)
			{
				throw new InvalidDataException("Ordinary recipe output differs from the audited single-craft rule.");
			}
			return amount;
		}

		public static void ValidateOrdinaryCraftingStation(Recipe recipe, int quality)
		{
			CraftingStation requiredStation = recipe.GetRequiredStation(quality);
			if ((Object)(object)requiredStation != (Object)null && requiredStation.m_upgrader)
			{
				throw new InvalidDataException("Special upgrader station acquisition requires a dedicated adapter.");
			}
		}

		public static int OrdinaryRequirementAmount(Requirement requirement, int quality)
		{
			if (requirement != null && !requirement.m_upgraderResource)
			{
				return requirement.GetAmount(quality);
			}
			return 0;
		}

		private static void ImportReviewedProcessors(Catalogue catalogue, Dictionary<string, List<CraftRoute>> routes, Dictionary<string, string> blocks, Smelter[] processors)
		{
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0325: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Expected O, but got Unknown
			KeyValuePair<string, string>[] reviewedProcessorOutputs = ReviewedProcessorOutputs;
			for (int i = 0; i < reviewedProcessorOutputs.Length; i++)
			{
				KeyValuePair<string, string> pair = reviewedProcessorOutputs[i];
				try
				{
					GameObject prefab = PrefabManager.Instance.GetPrefab(pair.Key);
					Smelter val = (((Object)(object)prefab == (Object)null) ? null : prefab.GetComponent<Smelter>());
					if ((Object)(object)val == (Object)null)
					{
						throw new InvalidDataException("Reviewed processor prefab is not loaded: " + pair.Key);
					}
					bool flag = ValidateReviewedProcessorCycle(val, pair.Key, pair.Value);
					ItemConversion[] array = ReviewedProcessorConversions(val, pair.Value);
					if (array.Length == 0)
					{
						throw new InvalidDataException("Reviewed output conversion is missing.");
					}
					List<CraftRoute> list = new List<CraftRoute>();
					ItemConversion[] array2 = array;
					foreach (ItemConversion val2 in array2)
					{
						string text = (flag ? "fuel only" : RequireLiveItemDefinition(catalogue, val2.m_from));
						ValidateProcessorSelection(val, val2);
						if (RequireLiveItemDefinition(catalogue, val2.m_to) != pair.Value)
						{
							throw new InvalidDataException("Processor output identity changed.");
						}
						List<Material> list2 = new List<Material>();
						if (!flag)
						{
							list2.Add(new Material(text, 1));
						}
						if (val.m_maxFuel > 0)
						{
							if (val.m_fuelPerProduct < 1)
							{
								throw new InvalidDataException("Processor fuel consumption is invalid.");
							}
							list2.Add(new Material(RequireLiveItemDefinition(catalogue, val.m_fuelItem), val.m_fuelPerProduct));
						}
						list.Add(new CraftRoute("Processor " + pair.Key + ": " + text + " -> " + pair.Value, 1, (IReadOnlyList<IReadOnlyList<Material>>)new IReadOnlyList<Material>[1] { list2 }, (string)null, 0.10m));
					}
					string text2 = ProcessorShape(val);
					foreach (Smelter item in processors.Where((Smelter x) => x.m_conversion.Any((ItemConversion c) => c != null && (Object)(object)c.m_to != (Object)null && PrefabName(((Component)c.m_to).gameObject) == pair.Value)))
					{
						if (PrefabName(((Component)item).gameObject) != pair.Key || ProcessorShape(item) != text2)
						{
							throw new InvalidDataException("An additional or changed processor route needs review.");
						}
					}
					if (!routes.TryGetValue(pair.Value, out var value))
					{
						value = (routes[pair.Value] = new List<CraftRoute>());
					}
					value.AddRange(list);
				}
				catch (Exception ex)
				{
					string text3 = "Processor acquisition estimated: " + ex.GetBaseException().Message;
					if (!routes.TryGetValue(pair.Value, out var value2))
					{
						value2 = (routes[pair.Value] = new List<CraftRoute>());
					}
					value2.Add(new CraftRoute("Processor " + pair.Key, 1, (IReadOnlyList<IReadOnlyList<Material>>)Array.Empty<IReadOnlyList<Material>>(), text3));
					catalogue.Warnings.Add(pair.Value + ": " + text3);
				}
			}
		}

		public static bool ValidateReviewedProcessorCycle(Smelter processor, string processorPrefab, string output)
		{
			if ((Object)(object)processor == (Object)null || PrefabName(((Component)processor).gameObject) != processorPrefab || !ReviewedProcessorOutputs.Any((KeyValuePair<string, string> pair) => pair.Key == processorPrefab && pair.Value == output) || processor.m_conversion == null || processor.m_conversion.Any((ItemConversion c) => c == null) || float.IsNaN(processor.m_secPerProduct) || float.IsInfinity(processor.m_secPerProduct) || processor.m_secPerProduct <= 0f || processor.m_secPerProduct % 1f != 0f || processor.m_maxFuel < 0 || processor.m_maxOre < 0)
			{
				throw new InvalidDataException("Processor no longer uses a reviewed production cycle.");
			}
			int num;
			if (processorPrefab == "piece_FrostKiln")
			{
				num = ((output == "FrozenFuel") ? 1 : 0);
				if (num != 0)
				{
					if (processor.m_maxOre != 0 || processor.m_maxFuel < 1 || processor.m_fuelPerProduct < 1 || (Object)(object)processor.m_fuelItem == (Object)null || (Object)(object)processor.m_windmill != (Object)null || processor.m_conversion.Count == 0 || processor.m_conversion.Any((ItemConversion c) => (Object)(object)c.m_from != (Object)null) || (processor.m_spawnStack && !GameFields.Required<bool>(processor, "m_noSourceConversion")))
					{
						throw new InvalidDataException("Fuel-only processor no longer uses the reviewed null-source cycle.");
					}
					goto IL_01e4;
				}
			}
			else
			{
				num = 0;
			}
			if (GameFields.Required<bool>(processor, "m_noSourceConversion") || processor.m_maxOre < 1 || ((Object)(object)processor.m_windmill != (Object)null && (processorPrefab != "windmill" || processor.m_maxFuel != 0)))
			{
				throw new InvalidDataException("Processor no longer uses the audited one-input/one-output cycle.");
			}
			goto IL_01e4;
			IL_01e4:
			return (byte)num != 0;
		}

		public static ItemConversion[] ReviewedProcessorConversions(Smelter processor, string output)
		{
			if ((Object)(object)processor == (Object)null || processor.m_conversion == null)
			{
				throw new InvalidDataException("Reviewed processor conversions are missing.");
			}
			List<ItemConversion> list = new List<ItemConversion>();
			foreach (ItemConversion conversion in processor.m_conversion.Where((ItemConversion c) => c != null && (Object)(object)c.m_to != (Object)null && PrefabName(((Component)c.m_to).gameObject) == output))
			{
				ValidateProcessorSelection(processor, conversion);
				if (!list.Any((ItemConversion c) => c.m_from == conversion.m_from && c.m_to == conversion.m_to))
				{
					list.Add(conversion);
				}
			}
			return list.ToArray();
		}

		private static string RequireLiveItemDefinition(Catalogue catalogue, ItemDrop drop)
		{
			if ((Object)(object)drop == (Object)null)
			{
				throw new InvalidDataException("Ingredient or output prefab is missing.");
			}
			string text = PrefabName(((Component)drop).gameObject);
			if (!catalogue.Items.TryGetValue(text, out var value) || value.Drop != drop || !IsLiveItem(catalogue, value))
			{
				throw new InvalidDataException("Item is not the exact live inventory definition: " + text);
			}
			return text;
		}

		public static void ValidateProcessorSelection(Smelter processor, ItemConversion conversion)
		{
			if ((Object)(object)processor == (Object)null || conversion == null || (Object)(object)conversion.m_to == (Object)null || ((Object)(object)conversion.m_from == (Object)null && (processor.m_maxOre != 0 || processor.m_maxFuel < 1)))
			{
				throw new InvalidDataException("Reviewed processor conversion is incomplete.");
			}
			MethodInfo? method = typeof(Smelter).GetMethod("GetItemConversion", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
			if (method == null)
			{
				throw new MissingMethodException("Smelter.GetItemConversion(string)");
			}
			object? obj = method.Invoke(processor, new object[1] { ((Object)(object)conversion.m_from == (Object)null) ? "" : ((Object)((Component)conversion.m_from).gameObject).name });
			ItemConversion val = (ItemConversion)((obj is ItemConversion) ? obj : null);
			if (val == null || val.m_from != conversion.m_from || val.m_to != conversion.m_to)
			{
				throw new InvalidDataException("An earlier processor conversion shadows this reviewed output.");
			}
		}

		private static IEnumerable<Smelter> RelevantProcessors()
		{
			return from x in Resources.FindObjectsOfTypeAll<Smelter>()
				where (Object)(object)x != (Object)null && x.m_conversion != null && x.m_conversion.Any((ItemConversion c) => c != null && (Object)(object)c.m_to != (Object)null && ReviewedProcessorOutputs.Any((KeyValuePair<string, string> pair) => pair.Value == PrefabName(((Component)c.m_to).gameObject)))
				select x;
		}

		private static string ProcessorShape(Smelter processor)
		{
			return string.Join("|", PrefabName(((Component)processor).gameObject), processor.m_maxOre.ToString(CultureInfo.InvariantCulture), processor.m_maxFuel.ToString(CultureInfo.InvariantCulture), processor.m_fuelPerProduct.ToString(CultureInfo.InvariantCulture), processor.m_secPerProduct.ToString("R", CultureInfo.InvariantCulture), Item(processor.m_fuelItem), GameFields.Required<bool>(processor, "m_noSourceConversion").ToString(), ((Object)(object)processor.m_windmill != (Object)null).ToString(), processor.m_spawnStack.ToString(), processor.m_requiresRoof.ToString(), string.Join(";", processor.m_conversion.Select((ItemConversion c) => (c != null) ? (Item(c.m_from) + ">" + Item(c.m_to)) : "null")));
			static string Item(ItemDrop item)
			{
				if (!((Object)(object)item == (Object)null))
				{
					return PrefabName(((Component)item).gameObject) + "#" + ((Object)item).GetInstanceID();
				}
				return "missing";
			}
		}

		private static string ReviewedProcessorSignature(IEnumerable<Smelter> processors = null)
		{
			try
			{
				return string.Join("\n", (processors ?? RelevantProcessors()).Select(ProcessorShape).Distinct().OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
			}
			catch
			{
				return "unavailable";
			}
		}

		public static bool IsLiveItem(Catalogue catalogue, CatalogueEntry entry)
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance != catalogue.Database || (Object)(object)entry?.Object == (Object)null || (Object)(object)entry.Drop == (Object)null)
			{
				return false;
			}
			if (instance.GetItemPrefab(entry.Prefab) != entry.Object || entry.Object.GetComponent<ItemDrop>() != entry.Drop || !GameFields.Required<List<GameObject>>(instance, "m_items").Any((GameObject x) => x == entry.Object))
			{
				return false;
			}
			SharedData val = entry.Drop.m_itemData?.m_shared;
			if (val != null && val.m_maxQuality == entry.MaxQuality)
			{
				return StackLimits.ForTemplate(entry.Drop.m_itemData) == entry.MaxStack;
			}
			return false;
		}

		public static IEnumerable<string> LiveItemIds(Catalogue catalogue)
		{
			ObjectDB database = ObjectDB.instance;
			if ((Object)(object)database == (Object)null || catalogue == null || database != catalogue.Database)
			{
				yield break;
			}
			HashSet<object> present = new HashSet<object>(database.m_items.Cast<object>(), InstalledModCatalogue.ObjectIdentityComparer.Instance);
			foreach (CatalogueEntry value in catalogue.Items.Values)
			{
				if (value.ShowInBrowser && !CataloguePolicy.IsExcluded(value.Prefab) && !((Object)(object)value.Object == (Object)null) && !((Object)(object)value.Drop == (Object)null) && present.Contains(value.Object) && database.GetItemPrefab(value.Prefab) == value.Object && value.Object.GetComponent<ItemDrop>() == value.Drop)
				{
					SharedData val = value.Drop.m_itemData?.m_shared;
					if (val != null && val.m_maxQuality == value.MaxQuality && StackLimits.ForTemplate(value.Drop.m_itemData) == value.MaxStack)
					{
						yield return value.Prefab;
					}
				}
			}
		}

		public static bool RuntimeStillCurrent(Catalogue catalogue)
		{
			if (catalogue == null || (Object)(object)ObjectDB.instance == (Object)null || catalogue.Database != ObjectDB.instance || catalogue.EpicApiInstalled != EpicLootBridge.IsInstalled || catalogue.PluginSignature != InstalledModCatalogue.CurrentPluginSignature() || catalogue.StackConfigurationSignature != StackLimits.ConfigurationSignature || catalogue.RecipeSnapshot == null || !catalogue.RecipeSnapshot.Matches(ObjectDB.instance.m_recipes) || catalogue.ProcessorSignature != ReviewedProcessorSignature() || (catalogue.SourceSnapshot != null && !catalogue.SourceSnapshot.Matches()) || (catalogue.EpicMaterialSnapshot != null && !catalogue.EpicMaterialSnapshot.Matches()))
			{
				return false;
			}
			List<GameObject> list = GameFields.Required<List<GameObject>>(ObjectDB.instance, "m_items");
			if (list.Count == catalogue.RuntimeObjects.Length)
			{
				return !list.Where((GameObject item, int index) => item != catalogue.RuntimeObjects[index]).Any();
			}
			return false;
		}

		public static CraftingStation FindStation(Player player, float distance)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			CraftingStation result = null;
			float num = distance;
			CraftingStation[] array = Object.FindObjectsByType<CraftingStation>((FindObjectsSortMode)0);
			foreach (CraftingStation val in array)
			{
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
				{
					float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)val).transform.position);
					if (num2 <= num)
					{
						num = num2;
						result = val;
					}
				}
			}
			return result;
		}

		public static string ExportAudit(Catalogue catalogue, string directory)
		{
			Directory.CreateDirectory(directory);
			string text = Path.Combine(directory, "catalogue-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fffffff") + ".csv");
			InstalledModSnapshot installedModSnapshot = InstalledModCatalogue.Capture(catalogue.Items.Values);
			StringBuilder stringBuilder = new StringBuilder("prefab,display_name,source,quality,max_stack,browser_visible,base_value,normal_value,biome,biome_multiplier,unit_value,status,reason,tier_minimum,biome_reason\n");
			foreach (CatalogueEntry item in catalogue.Items.Values.OrderBy<CatalogueEntry, string>((CatalogueEntry x) => x.Prefab, StringComparer.Ordinal))
			{
				for (int num = 1; num <= item.MaxQuality; num++)
				{
					PriceResult val = EpicLootBridge.PriceCanonicalReward(catalogue.Economy, item.Object, num);
					PriceResult val2 = catalogu

BepInEx/plugins/FateForge/FateForge.Core.dll

Decompiled 7 hours ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FateForge.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.8.0")]
[assembly: AssemblyInformationalVersion("0.4.8")]
[assembly: AssemblyProduct("FateForge.Core")]
[assembly: AssemblyTitle("FateForge.Core")]
[assembly: AssemblyVersion("0.4.8.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 FateForge.Core
{
	public enum ValuationBiome
	{
		Meadows,
		BlackForest,
		Ocean,
		Swamp,
		Mountain,
		Plains,
		Mistlands,
		AshLands,
		DeepNorth
	}
	public sealed class BiomeAdjustment
	{
		public ValuationBiome Biome { get; }

		public string Name => BiomeValuation.Name(Biome);

		public decimal Multiplier => BiomeValuation.Multiplier(Biome);

		public string Reason { get; }

		public BiomeAdjustment(ValuationBiome biome, string reason)
		{
			if (!Enum.IsDefined(typeof(ValuationBiome), biome))
			{
				throw new ArgumentOutOfRangeException("biome");
			}
			if (string.IsNullOrWhiteSpace(reason))
			{
				throw new ArgumentException("Biome provenance is required.", "reason");
			}
			Biome = biome;
			Reason = reason;
		}
	}
	public sealed class BiomeRecipe
	{
		public string Output { get; }

		public string Evidence { get; }

		public IReadOnlyList<string> Ingredients { get; }

		public ValuationBiome? MinimumBiome { get; }

		public string? PricingRouteId { get; }

		public BiomeRecipe(string output, IEnumerable<string> ingredients, string evidence, ValuationBiome? minimumBiome = null)
			: this(output, ingredients, evidence, minimumBiome, null)
		{
		}

		public BiomeRecipe(string output, IEnumerable<string> ingredients, string evidence, ValuationBiome? minimumBiome, string? pricingRouteId)
		{
			if (string.IsNullOrWhiteSpace(output) || string.IsNullOrWhiteSpace(evidence))
			{
				throw new ArgumentException("Recipe provenance is required.");
			}
			string[] array = ingredients?.Distinct<string>(StringComparer.Ordinal).ToArray() ?? throw new ArgumentNullException("ingredients");
			if (array.Length == 0 || array.Any(string.IsNullOrWhiteSpace))
			{
				throw new ArgumentException("Recipe inputs are required.");
			}
			Output = output;
			Evidence = evidence;
			Ingredients = new ReadOnlyCollection<string>(array);
			if (minimumBiome.HasValue && !Enum.IsDefined(typeof(ValuationBiome), minimumBiome.Value))
			{
				throw new ArgumentOutOfRangeException("minimumBiome");
			}
			MinimumBiome = minimumBiome;
			if (pricingRouteId != null && string.IsNullOrWhiteSpace(pricingRouteId))
			{
				throw new ArgumentException("Pricing route identity is required.", "pricingRouteId");
			}
			PricingRouteId = pricingRouteId;
		}
	}
	public static class BiomeValuation
	{
		private static readonly string[] Names = new string[9] { "Meadows", "Black Forest", "Ocean", "Swamp", "Mountain", "Plains", "Mistlands", "Ashlands", "Deep North" };

		private static readonly decimal[] Multipliers = new decimal[9] { 1m, 1.25m, 1.5m, 1.75m, 2.25m, 3m, 4m, 5.5m, 7.5m };

		public const decimal MaximumMultiplier = 7.5m;

		public static IReadOnlyList<BiomeAdjustment> Ordered { get; } = new ReadOnlyCollection<BiomeAdjustment>(new BiomeAdjustment[9]
		{
			Default(ValuationBiome.Meadows),
			Default(ValuationBiome.BlackForest),
			Default(ValuationBiome.Ocean),
			Default(ValuationBiome.Swamp),
			Default(ValuationBiome.Mountain),
			Default(ValuationBiome.Plains),
			Default(ValuationBiome.Mistlands),
			Default(ValuationBiome.AshLands),
			Default(ValuationBiome.DeepNorth)
		});

		public static BiomeAdjustment Unknown { get; } = new BiomeAdjustment(ValuationBiome.Meadows, "Biome unobserved; inferred Meadows baseline (no biome premium).");

		private static BiomeAdjustment Default(ValuationBiome biome)
		{
			return new BiomeAdjustment(biome, "Owner-selected biome balance multiplier.");
		}

		public static string Name(ValuationBiome biome)
		{
			return Names[Index(biome)];
		}

		public static decimal Multiplier(ValuationBiome biome)
		{
			return Multipliers[Index(biome)];
		}

		private static int Index(ValuationBiome biome)
		{
			if (!Enum.IsDefined(typeof(ValuationBiome), biome))
			{
				throw new ArgumentOutOfRangeException("biome");
			}
			return (int)biome;
		}

		public static ValuationBiome FromProgressionTier(int tier)
		{
			switch (tier)
			{
			default:
				throw new ArgumentOutOfRangeException("tier");
			case 2:
			case 3:
			case 4:
			case 5:
			case 6:
			case 7:
				return (ValuationBiome)(tier + 1);
			case 0:
			case 1:
				return (ValuationBiome)tier;
			}
		}

		public static int ProgressionTier(ValuationBiome biome)
		{
			if (biome > ValuationBiome.BlackForest)
			{
				if (biome != ValuationBiome.Ocean)
				{
					return Index(biome) - 1;
				}
				return 1;
			}
			return Index(biome);
		}

		public static BiomeAdjustment AtLeast(BiomeAdjustment origin, ValuationBiome minimum, string evidence)
		{
			if (origin == null)
			{
				throw new ArgumentNullException("origin");
			}
			Index(minimum);
			if (origin.Biome < minimum)
			{
				return new BiomeAdjustment(minimum, origin.Reason + " Minimum progression " + Name(minimum) + ": " + evidence);
			}
			return origin;
		}

		public static BiomeAdjustment? SpawnOrigin(BiomeAdjustment habitat, string? globalKey, string? persistentEvent, IReadOnlyDictionary<string, BiomeAdjustment> knownProgressionKeys)
		{
			if (habitat == null || knownProgressionKeys == null)
			{
				throw new ArgumentNullException();
			}
			BiomeAdjustment biomeAdjustment = habitat;
			string[] array = new string[2] { globalKey, persistentEvent };
			foreach (string text in array)
			{
				if (!string.IsNullOrEmpty(text))
				{
					if (!knownProgressionKeys.TryGetValue(text, out BiomeAdjustment value))
					{
						return null;
					}
					biomeAdjustment = AtLeast(biomeAdjustment, value.Biome, "Native spawn requires " + text + ". " + value.Reason);
				}
			}
			return biomeAdjustment;
		}

		public static PriceResult Apply(PriceResult normalPrice, BiomeAdjustment adjustment)
		{
			if (normalPrice == null || adjustment == null)
			{
				throw new ArgumentNullException();
			}
			if (!normalPrice.Available)
			{
				return normalPrice;
			}
			if (normalPrice.UnitValue > 1000000000000m / adjustment.Multiplier)
			{
				return PriceResult.Unavailable("Final biome-adjusted value exceeds the supported range.");
			}
			return PriceResult.Priced(normalPrice.UnitValue * adjustment.Multiplier, normalPrice.Reason + " Biome: " + adjustment.Name + " x" + adjustment.Multiplier.ToString("0.##", CultureInfo.InvariantCulture) + " applied once after normal value. " + adjustment.Reason);
		}

		public static IReadOnlyDictionary<string, BiomeAdjustment> ResolveRecipes(IReadOnlyDictionary<string, BiomeAdjustment> seeds, IReadOnlyDictionary<string, BiomeAdjustment> nativeSources, IEnumerable<BiomeRecipe> recipes, IReadOnlyDictionary<string, ValuationBiome>? minimumBiomes = null)
		{
			if (seeds == null || nativeSources == null || recipes == null)
			{
				throw new ArgumentNullException();
			}
			Dictionary<string, BiomeAdjustment> result = seeds.ToDictionary<KeyValuePair<string, BiomeAdjustment>, string, BiomeAdjustment>((KeyValuePair<string, BiomeAdjustment> x) => x.Key, (KeyValuePair<string, BiomeAdjustment> x) => ApplyMinimum(x.Key, x.Value), StringComparer.Ordinal);
			Dictionary<string, BiomeRecipe[]> routes = recipes.Where((BiomeRecipe r) => r != null && seeds.ContainsKey(r.Output) && r.Ingredients.All(seeds.ContainsKey)).GroupBy<BiomeRecipe, string>((BiomeRecipe r) => r.Output, StringComparer.Ordinal).ToDictionary<IGrouping<string, BiomeRecipe>, string, BiomeRecipe[]>((IGrouping<string, BiomeRecipe> g) => g.Key, (IGrouping<string, BiomeRecipe> g) => g.ToArray(), StringComparer.Ordinal);
			HashSet<string> resolved = new HashSet<string>(seeds.Keys.Where((string id) => !routes.ContainsKey(id)), StringComparer.Ordinal);
			foreach (KeyValuePair<string, BiomeAdjustment> item in nativeSources.Where<KeyValuePair<string, BiomeAdjustment>>((KeyValuePair<string, BiomeAdjustment> x) => seeds.ContainsKey(x.Key)))
			{
				result[item.Key] = ApplyMinimum(item.Key, item.Value);
				resolved.Add(item.Key);
			}
			for (int num = 0; num <= seeds.Count; num++)
			{
				bool flag = false;
				foreach (KeyValuePair<string, BiomeRecipe[]> item2 in routes.OrderBy<KeyValuePair<string, BiomeRecipe[]>, string>((KeyValuePair<string, BiomeRecipe[]> x) => x.Key, StringComparer.Ordinal))
				{
					BiomeAdjustment value;
					BiomeAdjustment biomeAdjustment = (nativeSources.TryGetValue(item2.Key, out value) ? ApplyMinimum(item2.Key, value) : null);
					foreach (BiomeRecipe item3 in item2.Value.Where((BiomeRecipe r) => r.Ingredients.All(resolved.Contains)))
					{
						var anon = (from id in item3.Ingredients
							select new
							{
								Id = id,
								Value = result[id]
							} into x
							orderby x.Value.Biome descending
							select x).First();
						BiomeAdjustment biomeAdjustment2 = ApplyMinimum(item2.Key, new BiomeAdjustment(anon.Value.Biome, "Inferred from the highest ingredient biome in " + item3.Evidence + ": " + anon.Id + ". " + anon.Value.Reason));
						if (item3.MinimumBiome.HasValue)
						{
							biomeAdjustment2 = AtLeast(biomeAdjustment2, item3.MinimumBiome.Value, item3.Evidence);
						}
						if (biomeAdjustment == null || biomeAdjustment2.Biome < biomeAdjustment.Biome)
						{
							biomeAdjustment = biomeAdjustment2;
						}
					}
					if (biomeAdjustment != null && (!resolved.Contains(item2.Key) || result[item2.Key].Biome != biomeAdjustment.Biome))
					{
						result[item2.Key] = biomeAdjustment;
						resolved.Add(item2.Key);
						flag = true;
					}
				}
				if (!flag)
				{
					break;
				}
			}
			return new ReadOnlyDictionary<string, BiomeAdjustment>(result);
			BiomeAdjustment ApplyMinimum(string id, BiomeAdjustment biomeAdjustment3)
			{
				if (minimumBiomes == null || !minimumBiomes.TryGetValue(id, out var value2))
				{
					return biomeAdjustment3;
				}
				return AtLeast(biomeAdjustment3, value2, "Reviewed resource calibration cannot be discounted by a later spawn or conversion.");
			}
		}

		public static IReadOnlyDictionary<string, BiomeAdjustment> ResolveRouteBiomes(IReadOnlyDictionary<string, BiomeAdjustment> finalizedBiomes, IEnumerable<BiomeRecipe> recipes)
		{
			if (finalizedBiomes == null || recipes == null)
			{
				throw new ArgumentNullException();
			}
			Dictionary<string, BiomeAdjustment> dictionary = new Dictionary<string, BiomeAdjustment>(StringComparer.Ordinal);
			foreach (BiomeRecipe recipe in recipes)
			{
				if (recipe != null && recipe.PricingRouteId != null && finalizedBiomes.ContainsKey(recipe.Output) && !recipe.Ingredients.Any((string id) => !finalizedBiomes.ContainsKey(id)))
				{
					BiomeAdjustment biomeAdjustment = (from id in recipe.Ingredients
						select finalizedBiomes[id] into b
						orderby b.Biome descending
						select b).First();
					BiomeAdjustment biomeAdjustment2 = new BiomeAdjustment(biomeAdjustment.Biome, "Original acquisition for pricing route " + recipe.PricingRouteId + ": " + recipe.Evidence + ". " + biomeAdjustment.Reason);
					if (recipe.MinimumBiome.HasValue)
					{
						biomeAdjustment2 = AtLeast(biomeAdjustment2, recipe.MinimumBiome.Value, recipe.Evidence);
					}
					string key = recipe.Output + "\n" + recipe.PricingRouteId;
					if (!dictionary.TryGetValue(key, out var value) || biomeAdjustment2.Biome < value.Biome)
					{
						dictionary[key] = biomeAdjustment2;
					}
				}
			}
			return new ReadOnlyDictionary<string, BiomeAdjustment>(dictionary);
		}
	}
	public static class CataloguePolicy
	{
		private static readonly HashSet<string> Excluded = CreateExclusions();

		private static readonly HashSet<string> Feasts = new HashSet<string>(new string[9] { "FeastMeadows", "FeastBlackforest", "FeastSwamps", "FeastMountains", "FeastPlains", "FeastMistlands", "FeastAshlands", "FeastDeepNorth", "FeastOceans" }, StringComparer.Ordinal);

		public static IReadOnlyList<string> ExcludedPrefabs { get; } = new ReadOnlyCollection<string>(Excluded.OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray());

		private static HashSet<string> CreateExclusions()
		{
			HashSet<string> hashSet = new HashSet<string>(new string[31]
			{
				"HealthUpgrade_Bonemass", "HealthUpgrade_GDKing", "StaminaUpgrade_Greydwarf", "StaminaUpgrade_Troll", "StaminaUpgrade_Wraith", "VegvisirShard_Bonemass", "StoneRock", "IceShoes", "IceSkates", "TorchMist",
				"CapeTest", "ShieldKnight", "TrophyDeerWhite", "CapeOdin", "HelmetOdin", "Pot_Shard_Red", "Larva", "TurretBoltBone", "SmallPartsGoldUncooked", "GenericMoldUncooked",
				"MoldSmallParts", "LastBossGate_RuneTile", "_Unidentified", "DvergerArbalest", "DvergerArbalest_shoot", "DvergerArbalest_shootAshlands", "DvergerArbalest_shootDeepNorth", "SwordIronFire", "SP_ArmorDress1", "SP_ArmorLeatherLegs",
				"SP_ArmorTunic5"
			}, StringComparer.Ordinal);
			string[] array = new string[6] { "Magic", "Rare", "Epic", "Legendary", "Mythic", "Ancient" };
			foreach (string text in array)
			{
				hashSet.Add("Land_" + text + "_Unidentified");
			}
			array = new string[25]
			{
				"ArmorBronzeChest", "ArmorBronzeLegs", "ArmorFenringChest", "ArmorFenringLegs", "ArmorMageChest", "ArmorMageChest_Ashlands", "ArmorMageLegs", "ArmorMageLegs_Ashlands", "ArmorPaddedCuirass", "ArmorPaddedGreaves",
				"ArmorTrollLeatherChest", "ArmorTrollLeatherLegs", "AxeBronze", "BattleaxeCrystal", "BowDraugrFang", "CapeLinen", "CapeTrollHide", "CapeWolf", "HelmetBronze", "KnifeSilver",
				"KnifeSkollAndHati", "ShieldBlackmetalTower", "StaffFireball", "StaffLightning", "SwordBlackmetal"
			};
			foreach (string text2 in array)
			{
				hashSet.Add("FW_" + text2);
				hashSet.Add("SP_" + text2);
			}
			return hashSet;
		}

		public static bool IsExcluded(string? prefab)
		{
			if (prefab != null)
			{
				return Excluded.Contains(prefab);
			}
			return false;
		}

		public static string? ExclusionReason(string? prefab)
		{
			if (!IsExcluded(prefab))
			{
				return null;
			}
			return "Excluded developer, unused, restricted or NPC-only item; not available for gambling.";
		}

		public static string DisplayName(string prefab, string localized)
		{
			switch (prefab)
			{
			case "Voidplasm":
				return "Voidplasm";
			case "FishAnglerRaw":
				return "Raw Anglerfish";
			case "TrophyDraugrFem":
				return localized + " (variant)";
			case "TrophyFrostTroll":
				return "Frost Troll Trophy";
			default:
				if (Feasts.Contains(prefab))
				{
					return localized + " (serving)";
				}
				if (prefab.EndsWith("_Material", StringComparison.Ordinal) && Feasts.Contains(prefab.Substring(0, prefab.Length - "_Material".Length)))
				{
					return localized + " (platter)";
				}
				return localized;
			}
		}
	}
	public enum DifficultyItemKind
	{
		Material,
		Trophy,
		Food,
		Weapon,
		Armor,
		Ammunition,
		Utility
	}
	public sealed class DifficultyQualityFacts
	{
		public decimal Damage { get; }

		public decimal Armor { get; }

		public decimal Durability { get; }

		public DifficultyQualityFacts(decimal damage = 0m, decimal armor = 0m, decimal durability = 0m)
		{
			Damage = Nonnegative(damage);
			Armor = Nonnegative(armor);
			Durability = Nonnegative(durability);
		}

		private static decimal Nonnegative(decimal value)
		{
			if (!(value >= 0m) || !(value <= 1000000000m))
			{
				throw new ArgumentOutOfRangeException("value");
			}
			return value;
		}
	}
	public sealed class DifficultyItemFacts
	{
		public string Prefab { get; }

		public DifficultyItemKind Kind { get; }

		public int Tier { get; }

		public decimal FoodPower { get; }

		public decimal CoinValue { get; }

		public IReadOnlyList<DifficultyQualityFacts> Qualities { get; }

		public string Evidence { get; }

		public DifficultyItemFacts(string prefab, DifficultyItemKind kind, int tier, IEnumerable<DifficultyQualityFacts> qualities, decimal foodPower = 0m, decimal coinValue = 0m, string evidence = "")
		{
			if (string.IsNullOrWhiteSpace(prefab) || !Enum.IsDefined(typeof(DifficultyItemKind), kind))
			{
				throw new ArgumentException("Invalid item facts.");
			}
			DifficultyValuation.TierBase(tier);
			DifficultyQualityFacts[] array = qualities?.ToArray() ?? throw new ArgumentNullException("qualities");
			if (array.Length < 1 || array.Length > 100 || array.Any((DifficultyQualityFacts q) => q == null))
			{
				throw new ArgumentException("Invalid quality observations.");
			}
			if (foodPower < 0m || foodPower > 1000000000m || coinValue < 0m || coinValue > 1000000000m)
			{
				throw new ArgumentOutOfRangeException();
			}
			Prefab = prefab;
			Kind = kind;
			Tier = tier;
			FoodPower = foodPower;
			CoinValue = coinValue;
			Qualities = new ReadOnlyCollection<DifficultyQualityFacts>(array);
			Evidence = evidence ?? "";
		}
	}
	public sealed class DifficultyDropFacts
	{
		public string Prefab { get; }

		public decimal Chance { get; }

		public decimal AverageQuantity { get; }

		public decimal? KnownBaseValue { get; }

		public DifficultyDropFacts(string prefab, decimal chance, decimal averageQuantity, decimal? knownBaseValue = null)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				throw new ArgumentException("Drop identifier is missing.");
			}
			if (!(chance < 0m) && !(chance > 1m) && !(averageQuantity <= 0m) && !(averageQuantity > 1000000m))
			{
				if (!knownBaseValue.HasValue)
				{
					goto IL_00b0;
				}
				decimal? num = knownBaseValue;
				if (!((num.GetValueOrDefault() <= default(decimal)) & num.HasValue))
				{
					num = knownBaseValue;
					decimal num2 = 1000000000000m;
					if (!((num.GetValueOrDefault() > num2) & num.HasValue))
					{
						goto IL_00b0;
					}
				}
			}
			throw new ArgumentOutOfRangeException();
			IL_00b0:
			Prefab = prefab;
			Chance = chance;
			AverageQuantity = averageQuantity;
			KnownBaseValue = knownBaseValue;
		}
	}
	public sealed class DifficultyEstimate
	{
		public IReadOnlyList<decimal> BaseValues { get; }

		public decimal BaseValue => BaseValues[0];

		public decimal MarkupRate { get; }

		public IReadOnlyList<decimal> NormalValues { get; }

		public string Reason { get; }

		public DifficultyEstimate(IEnumerable<decimal> baseValues, decimal markupRate, string reason, IEnumerable<decimal>? normalValues = null)
		{
			decimal[] values = baseValues?.ToArray() ?? throw new ArgumentNullException("baseValues");
			if (values.Length < 1 || values.Length > 100 || values.Any((decimal v) => v <= 0m || v > 1000000000000m))
			{
				throw new ArgumentOutOfRangeException("baseValues");
			}
			if (markupRate != 0m && markupRate != 0.10m && markupRate != 0.20m)
			{
				throw new ArgumentOutOfRangeException("markupRate");
			}
			if (string.IsNullOrWhiteSpace(reason))
			{
				throw new ArgumentException("An estimate requires its evidence and method.", "reason");
			}
			BaseValues = new ReadOnlyCollection<decimal>(values);
			MarkupRate = markupRate;
			Reason = reason;
			decimal[] array = normalValues?.ToArray() ?? values.Select((decimal v) => v * (1m + markupRate)).ToArray();
			if (array.Length != values.Length || array.Where((decimal v, int i) => v < values[i] || v > 1200000000000.0m).Any())
			{
				throw new ArgumentOutOfRangeException("normalValues");
			}
			NormalValues = new ReadOnlyCollection<decimal>(array);
		}
	}
	public static class DifficultyValuation
	{
		private static readonly decimal[] TierBases = new decimal[8] { 0.3m, 25m, 120m, 300m, 750m, 1500m, 3500m, 8000m };

		public const decimal CoinBaseValue = 0.3m;

		public const string CoinPrefab = "Coins";

		public static decimal TierBase(int tier)
		{
			if (tier < 0 || tier >= TierBases.Length)
			{
				throw new ArgumentOutOfRangeException("tier");
			}
			return TierBases[tier];
		}

		public static int TierFromStats(decimal damage, decimal armor, decimal foodPower, int toolTier)
		{
			if (damage < 0m || armor < 0m || foodPower < 0m || toolTier < 0)
			{
				throw new ArgumentOutOfRangeException();
			}
			int val = 0;
			decimal[] array = new decimal[7] { 30m, 50m, 70m, 90m, 110m, 140m, 180m };
			decimal[] array2 = new decimal[7] { 8m, 14m, 20m, 26m, 32m, 38m, 44m };
			decimal[] array3 = new decimal[7] { 45m, 70m, 90m, 110m, 140m, 170m, 210m };
			for (int i = 0; i < 7; i++)
			{
				if (damage >= array[i] || armor >= array2[i] || foodPower >= array3[i])
				{
					val = i + 1;
				}
			}
			return Math.Max(val, Math.Min(7, toolTier));
		}

		public static DifficultyQualityFacts RelevantStats(DifficultyItemKind kind, DifficultyQualityFacts facts)
		{
			if (facts == null)
			{
				throw new ArgumentNullException("facts");
			}
			return new DifficultyQualityFacts((kind == DifficultyItemKind.Weapon || kind == DifficultyItemKind.Ammunition) ? facts.Damage : 0m, (kind == DifficultyItemKind.Armor) ? facts.Armor : 0m, (kind == DifficultyItemKind.Weapon || kind == DifficultyItemKind.Armor || kind == DifficultyItemKind.Utility) ? facts.Durability : 0m);
		}

		public static int TierFromItem(DifficultyItemKind kind, DifficultyQualityFacts facts, decimal foodPower, int genuineToolTier)
		{
			DifficultyQualityFacts difficultyQualityFacts = RelevantStats(kind, facts);
			return TierFromStats(difficultyQualityFacts.Damage, difficultyQualityFacts.Armor, (kind == DifficultyItemKind.Food) ? foodPower : 0m, (kind == DifficultyItemKind.Weapon || kind == DifficultyItemKind.Utility) ? genuineToolTier : 0);
		}

		public static DifficultyEstimate EstimateItem(DifficultyItemFacts facts)
		{
			if (facts == null)
			{
				throw new ArgumentNullException("facts");
			}
			if (facts.Prefab == "Coins")
			{
				return new DifficultyEstimate(Enumerable.Repeat(0.3m, facts.Qualities.Count), 0m, "Relative currency calibration: one Coin has base value 0.3, shared by trader prices and creature drop accounting.");
			}
			decimal num = TierBase(facts.Tier);
			List<decimal> list = new List<decimal>();
			decimal num2 = default(decimal);
			for (int i = 0; i < facts.Qualities.Count; i++)
			{
				DifficultyQualityFacts difficultyQualityFacts = RelevantStats(facts.Kind, facts.Qualities[i]);
				decimal num3 = difficultyQualityFacts.Damage * difficultyQualityFacts.Damage * 0.02m + difficultyQualityFacts.Armor * difficultyQualityFacts.Armor * 0.3m;
				decimal num4 = facts.Kind switch
				{
					DifficultyItemKind.Trophy => num * 10m, 
					DifficultyItemKind.Weapon => Math.Max(num * 8m, num3), 
					DifficultyItemKind.Armor => Math.Max(num * 6m, num3), 
					DifficultyItemKind.Ammunition => Math.Max(num * 0.1m, difficultyQualityFacts.Damage * 0.015m), 
					DifficultyItemKind.Food => Math.Max(num * 2m, facts.FoodPower / 20m), 
					DifficultyItemKind.Utility => num * 4m, 
					_ => num, 
				};
				if (i == 0)
				{
					num2 = num3;
				}
				if (i > 0 && (facts.Kind == DifficultyItemKind.Weapon || facts.Kind == DifficultyItemKind.Armor))
				{
					num4 = Math.Max(num4, list[0] + Math.Max(0m, num3 - num2));
				}
				if (i > 0 && facts.Kind == DifficultyItemKind.Utility && facts.Qualities[0].Durability > 0m)
				{
					decimal num5 = num * Math.Max(0m, difficultyQualityFacts.Durability - facts.Qualities[0].Durability);
					decimal num6 = 833333333333.3333333333333333m - num4;
					num4 = ((num5 > 0m && facts.Qualities[0].Durability < num5 / num6) ? 833333333333.3333333333333333m : (num4 + num5 / facts.Qualities[0].Durability));
				}
				num4 = Math.Max(num4, facts.CoinValue * 0.3m);
				list.Add(Math.Min(833333333333.3333333333333333m, num4));
			}
			decimal markupRate = ((facts.Kind == DifficultyItemKind.Weapon || facts.Kind == DifficultyItemKind.Armor || facts.Kind == DifficultyItemKind.Ammunition || facts.Kind == DifficultyItemKind.Utility) ? 0.20m : 0m);
			return new DifficultyEstimate(list, markupRate, "Difficulty estimate: tier " + facts.Tier + ", " + facts.Kind.ToString() + ", observed native stats/quality and coin value. " + facts.Evidence);
		}

		public static IReadOnlyDictionary<string, DifficultyEstimate> AllocateEncounter(int tier, decimal health, bool boss, IEnumerable<DifficultyDropFacts> dropFacts, string evidence = "")
		{
			TierBase(tier);
			if (health < 0m || health > 1000000000m)
			{
				throw new ArgumentOutOfRangeException("health");
			}
			DifficultyDropFacts[] array = (from d in dropFacts?.Where((DifficultyDropFacts d) => d != null && d.Chance > 0m)
				select (!(d.Prefab == "Coins")) ? d : new DifficultyDropFacts(d.Prefab, d.Chance, d.AverageQuantity, 0.3m)).ToArray() ?? throw new ArgumentNullException("dropFacts");
			Dictionary<string, DifficultyEstimate> dictionary = new Dictionary<string, DifficultyEstimate>(StringComparer.Ordinal);
			if (array.Length == 0)
			{
				return new ReadOnlyDictionary<string, DifficultyEstimate>(dictionary);
			}
			decimal val = Math.Max(1.8m * (1m + (decimal)tier), health * 0.1m) * (boss ? 8m : 1m);
			decimal num = array.Where((DifficultyDropFacts d) => d.KnownBaseValue.HasValue).Sum((DifficultyDropFacts d) => d.Chance * d.AverageQuantity * d.KnownBaseValue.Value);
			IGrouping<string, DifficultyDropFacts>[] array2 = array.Where((DifficultyDropFacts d) => !d.KnownBaseValue.HasValue).GroupBy<DifficultyDropFacts, string>((DifficultyDropFacts d) => d.Prefab, StringComparer.Ordinal).ToArray();
			if (array2.Length == 0)
			{
				return new ReadOnlyDictionary<string, DifficultyEstimate>(dictionary);
			}
			val = Math.Max(val, num * 2m);
			decimal num2 = (val - num) / (decimal)array2.Length;
			IGrouping<string, DifficultyDropFacts>[] array3 = array2;
			foreach (IGrouping<string, DifficultyDropFacts> grouping in array3)
			{
				decimal num4 = grouping.Sum((DifficultyDropFacts d) => d.Chance * d.AverageQuantity);
				decimal num5 = 833333333333.3333333333333333m;
				decimal num6 = ((num4 < num2 / num5) ? num5 : (num2 / num4));
				dictionary.Add(grouping.Key, new DifficultyEstimate(new decimal[1] { num6 }, 0m, "Difficulty estimate: " + (boss ? "whole boss" : "whole creature") + " drop bundle budget " + val.ToString("0.####", CultureInfo.InvariantCulture) + "; expected units " + num4.ToString("0.########", CultureInfo.InvariantCulture) + "; tier " + tier + "; HP " + health.ToString(CultureInfo.InvariantCulture) + ". " + evidence));
			}
			return new ReadOnlyDictionary<string, DifficultyEstimate>(dictionary);
		}

		public static DifficultyEstimate FromAcquisition(decimal baseValue, string evidence)
		{
			if (baseValue <= 0m || baseValue > 1000000000000m)
			{
				throw new ArgumentOutOfRangeException("baseValue");
			}
			return new DifficultyEstimate(new decimal[1] { baseValue }, 0m, "Difficulty estimate from observed acquisition: " + evidence);
		}

		public static DifficultyEstimate PreserveSaleValue(DifficultyEstimate acquisition, decimal nativeCoinValue)
		{
			if (acquisition == null)
			{
				throw new ArgumentNullException("acquisition");
			}
			if (nativeCoinValue < 0m || nativeCoinValue > 1000000000m)
			{
				throw new ArgumentOutOfRangeException("nativeCoinValue");
			}
			decimal saleBase = nativeCoinValue * 0.3m;
			if (acquisition.BaseValues.All((decimal value) => value >= saleBase))
			{
				return acquisition;
			}
			decimal[] bases = acquisition.BaseValues.Select((decimal value) => Math.Max(value, saleBase)).ToArray();
			decimal[] normalValues = acquisition.NormalValues.Select((decimal value, int i) => Math.Max(value, bases[i] * (1m + acquisition.MarkupRate))).ToArray();
			return new DifficultyEstimate(bases, acquisition.MarkupRate, acquisition.Reason + " Preserved loaded item sale value: " + nativeCoinValue.ToString(CultureInfo.InvariantCulture) + " Coins at base " + 0.3m.ToString(CultureInfo.InvariantCulture) + " each.", normalValues);
		}

		public static DifficultyEstimate CommonGathering(int tier, int outputCount, string evidence)
		{
			TierBase(tier);
			if (outputCount < 1)
			{
				throw new ArgumentOutOfRangeException("outputCount");
			}
			return FromAcquisition(0.3m * (1m + (decimal)tier * 0.5m) / (decimal)outputCount, "Common gathering effort 0.3 * (1 + 0.5 * tier), tier " + tier + ", native batch " + outputCount + ". Final biome adjustment is separate. " + evidence);
		}

		public static decimal TraderBase(int coinPrice, int outputCount)
		{
			if (coinPrice < 1 || outputCount < 1)
			{
				throw new ArgumentOutOfRangeException();
			}
			return (decimal)coinPrice * 0.3m / (decimal)outputCount;
		}
	}
	public static class DurableWeaponValuation
	{
		public const decimal TierWeight = 8m;

		public static bool IsEligible(string? nativeItemType, bool usesDurability, decimal baseDamage)
		{
			if (baseDamage < 0m)
			{
				throw new ArgumentOutOfRangeException("baseDamage");
			}
			if (!usesDurability || baseDamage == 0m)
			{
				return false;
			}
			switch (nativeItemType)
			{
			default:
				return nativeItemType == "Bow";
			case "OneHandedWeapon":
			case "TwoHandedWeapon":
			case "TwoHandedWeaponLeft":
				return true;
			}
		}

		public static decimal Minimum(ValuationBiome biome)
		{
			return TierBaseValuation.Minimum(biome) * 8m;
		}
	}
	public sealed class Economy
	{
		private sealed class ConversionConstraint
		{
			public readonly string OutputKey;

			public readonly int Quality;

			public readonly CraftRoute Route;

			public ConversionConstraint(string key, int quality, CraftRoute route)
			{
				OutputKey = key;
				Quality = quality;
				Route = route;
			}
		}

		private sealed class RoutePrice
		{
			public bool Available { get; }

			public decimal Total { get; }

			public string Reason { get; }

			private RoutePrice(bool available, decimal total, string reason)
			{
				Available = available;
				Total = total;
				Reason = reason;
			}

			public static RoutePrice Priced(decimal value, string reason)
			{
				return new RoutePrice(available: true, value, reason);
			}

			public static RoutePrice Unavailable(string reason)
			{
				return new RoutePrice(available: false, 0m, reason);
			}
		}

		private bool _conversionPricesReady;

		public const decimal CraftingMarkupRate = 0.20m;

		public const decimal RefinedMarkupRate = 0.10m;

		private readonly IReadOnlyDictionary<string, ItemSpec> _items;

		private readonly IReadOnlyDictionary<string, DifficultyEstimate> _estimates;

		private readonly IReadOnlyDictionary<string, BiomeAdjustment> _biomes;

		private readonly IReadOnlyDictionary<string, decimal> _tierMinimums;

		private readonly IReadOnlyDictionary<string, BiomeAdjustment> _routeBiomes;

		private readonly Dictionary<string, PriceResult> _cache = new Dictionary<string, PriceResult>();

		private void EnsureConversionPrices()
		{
			if (_conversionPricesReady)
			{
				return;
			}
			Dictionary<string, PriceResult> dictionary = new Dictionary<string, PriceResult>(StringComparer.Ordinal);
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal);
			List<ConversionConstraint> list = new List<ConversionConstraint>();
			foreach (ItemSpec item in _items.Values.OrderBy<ItemSpec, string>((ItemSpec x) => x.Prefab, StringComparer.Ordinal))
			{
				for (int num = 1; num <= item.MaxQuality; num++)
				{
					string key = "priced:" + item.Prefab + ":" + num;
					PriceResult normalPrice = ResolveUnbounded(item.Prefab, num, new HashSet<string>(StringComparer.Ordinal), 0, includeMarkup: true);
					PriceResult priceResult = (dictionary[key] = ApplyFinalAdjustments(item.Prefab, normalPrice));
					dictionary2[key] = priceResult.Reason;
					if (!priceResult.Available || !item.LimitConversionValue)
					{
						continue;
					}
					foreach (CraftRoute item2 in item.Routes.OrderBy<CraftRoute, string>((CraftRoute x) => x.Id, StringComparer.Ordinal))
					{
						if (item2.UnsupportedReason == null && item2.CostsByQuality.Count >= num && (num == 1 || (item2.OutputCount == 1 && item.MaxStack == 1)))
						{
							list.Add(new ConversionConstraint(key, num, item2));
						}
					}
				}
			}
			for (int num2 = 0; num2 <= dictionary.Count; num2++)
			{
				bool flag = false;
				foreach (ConversionConstraint item3 in list)
				{
					decimal? num3 = ConversionCeiling(item3, dictionary);
					PriceResult priceResult3 = dictionary[item3.OutputKey];
					if (num3.HasValue && !(num3.Value >= priceResult3.UnitValue))
					{
						if (num3.Value <= 0m)
						{
							throw new InvalidOperationException("Unsupported cyclic conversion configuration: prices reached zero within decimal precision. Review active crafting settings.");
						}
						dictionary[item3.OutputKey] = PriceResult.Priced(num3.Value, dictionary2[item3.OutputKey] + " Conversion ceiling " + num3.Value.ToString(CultureInfo.InvariantCulture) + " from " + item3.Route.Id + ": actual final ingredient values / batch, plus one output markup; after biome/category premiums.");
						flag = true;
					}
				}
				if (flag)
				{
					continue;
				}
				foreach (KeyValuePair<string, PriceResult> item4 in dictionary)
				{
					_cache[item4.Key] = item4.Value;
				}
				_conversionPricesReady = true;
				return;
			}
			throw new InvalidOperationException("Unsupported cyclic conversion configuration: prices did not stabilize within the bounded solver budget. This does not prove a value-creating cycle; review active crafting settings.");
		}

		private static decimal? ConversionCeiling(ConversionConstraint constraint, IReadOnlyDictionary<string, PriceResult> values)
		{
			try
			{
				decimal num = default(decimal);
				for (int i = 0; i < constraint.Quality; i++)
				{
					decimal num2 = default(decimal);
					foreach (Material item in constraint.Route.CostsByQuality[i])
					{
						if (item.Amount != 0)
						{
							if (!values.TryGetValue("priced:" + item.Prefab + ":" + item.Quality, out PriceResult value) || !value.Available)
							{
								return null;
							}
							num2 += (decimal)item.Amount * value.UnitValue;
						}
					}
					if (num2 <= 0m)
					{
						return null;
					}
					num += ((i == 0) ? (num2 / (decimal)constraint.Route.OutputCount) : num2);
				}
				return num * (1m + constraint.Route.MarkupRate);
			}
			catch (OverflowException)
			{
				return null;
			}
		}

		public Economy(IEnumerable<ItemSpec> items, IReadOnlyDictionary<string, DifficultyEstimate>? estimates, IReadOnlyDictionary<string, BiomeAdjustment>? biomes, IReadOnlyDictionary<string, decimal>? tierMinimums)
			: this(items, estimates, biomes, tierMinimums, null)
		{
		}

		public Economy(IEnumerable<ItemSpec> items, IReadOnlyDictionary<string, DifficultyEstimate>? estimates = null, IReadOnlyDictionary<string, BiomeAdjustment>? biomes = null, IReadOnlyDictionary<string, decimal>? tierMinimums = null, IReadOnlyDictionary<string, BiomeAdjustment>? routeBiomes = null)
		{
			_items = items.ToDictionary<ItemSpec, string>((ItemSpec x) => x.Prefab, StringComparer.Ordinal);
			_estimates = ((estimates == null) ? new Dictionary<string, DifficultyEstimate>(StringComparer.Ordinal) : estimates.ToDictionary<KeyValuePair<string, DifficultyEstimate>, string, DifficultyEstimate>((KeyValuePair<string, DifficultyEstimate> x) => x.Key, (KeyValuePair<string, DifficultyEstimate> x) => x.Value, StringComparer.Ordinal));
			_biomes = ((biomes == null) ? new Dictionary<string, BiomeAdjustment>(StringComparer.Ordinal) : biomes.ToDictionary<KeyValuePair<string, BiomeAdjustment>, string, BiomeAdjustment>((KeyValuePair<string, BiomeAdjustment> x) => x.Key, (KeyValuePair<string, BiomeAdjustment> x) => x.Value, StringComparer.Ordinal));
			_tierMinimums = ((tierMinimums == null) ? new Dictionary<string, decimal>(StringComparer.Ordinal) : tierMinimums.ToDictionary<KeyValuePair<string, decimal>, string, decimal>((KeyValuePair<string, decimal> x) => x.Key, (KeyValuePair<string, decimal> x) => x.Value, StringComparer.Ordinal));
			_routeBiomes = ((routeBiomes == null) ? new Dictionary<string, BiomeAdjustment>(StringComparer.Ordinal) : routeBiomes.ToDictionary<KeyValuePair<string, BiomeAdjustment>, string, BiomeAdjustment>((KeyValuePair<string, BiomeAdjustment> x) => x.Key, (KeyValuePair<string, BiomeAdjustment> x) => x.Value, StringComparer.Ordinal));
			if (_tierMinimums.Values.Any((decimal v) => v <= 0m || v > 1000000000000m))
			{
				throw new ArgumentOutOfRangeException("tierMinimums");
			}
		}

		public PriceResult Price(string prefab, int quality = 1)
		{
			return CachedPrice(prefab, quality, includeMarkup: true);
		}

		public PriceResult BasePrice(string prefab, int quality = 1)
		{
			return CachedPrice(prefab, quality, includeMarkup: false);
		}

		public decimal TierMinimumFor(string prefab)
		{
			if (!_tierMinimums.TryGetValue(prefab, out var value))
			{
				return 0m;
			}
			return value;
		}

		public bool LimitsConversionValue(string prefab)
		{
			if (_items.TryGetValue(prefab, out ItemSpec value))
			{
				return value.LimitConversionValue;
			}
			return false;
		}

		private decimal OriginalBase(string prefab, decimal value)
		{
			return Math.Max(value, TierMinimumFor(prefab));
		}

		public BiomeAdjustment BiomeFor(string prefab)
		{
			if (!_biomes.TryGetValue(prefab, out BiomeAdjustment value))
			{
				return BiomeValuation.Unknown;
			}
			return value;
		}

		public PriceResult ApplyBiomeAdjustment(string prefab, PriceResult normalPrice)
		{
			if (!_biomes.TryGetValue(prefab, out BiomeAdjustment value))
			{
				return normalPrice;
			}
			return BiomeValuation.Apply(normalPrice, value);
		}

		public PriceResult ApplyFinalAdjustments(string prefab, PriceResult normalPrice)
		{
			string text = CataloguePolicy.ExclusionReason(prefab);
			if (text != null)
			{
				return PriceResult.Unavailable(text);
			}
			PriceResult priceResult = ApplyBiomeAdjustment(prefab, normalPrice);
			if (!_items.TryGetValue(prefab, out ItemSpec value) || !value.IsTrophy)
			{
				return priceResult;
			}
			return TrophyValuation.Apply(priceResult);
		}

		private PriceResult CachedPrice(string prefab, int quality, bool includeMarkup)
		{
			string key = (includeMarkup ? "priced:" : "base:") + prefab + ":" + quality;
			if (_cache.TryGetValue(key, out PriceResult value))
			{
				return value;
			}
			if (includeMarkup)
			{
				EnsureConversionPrices();
				if (_cache.TryGetValue(key, out value))
				{
					return value;
				}
			}
			PriceResult priceResult = Resolve(prefab, quality, new HashSet<string>(StringComparer.Ordinal), 0, includeMarkup);
			if (includeMarkup)
			{
				priceResult = ApplyFinalAdjustments(prefab, priceResult);
			}
			_cache[key] = priceResult;
			return priceResult;
		}

		private PriceResult Resolve(string prefab, int quality, HashSet<string> path, int depth, bool includeMarkup)
		{
			return ResolveUnbounded(prefab, quality, path, depth, includeMarkup);
		}

		private PriceResult ResolveUnbounded(string prefab, int quality, HashSet<string> path, int depth, bool includeMarkup)
		{
			PriceResult priceResult = ResolveExact(prefab, quality, path, depth, includeMarkup);
			if (priceResult.Available || !_items.TryGetValue(prefab, out ItemSpec value) || value.BlockReason != null || quality < 1 || quality > value.MaxQuality || !_estimates.TryGetValue(prefab, out DifficultyEstimate value2) || quality > value2.BaseValues.Count)
			{
				return priceResult;
			}
			decimal num = value2.BaseValues[quality - 1];
			decimal num2 = OriginalBase(prefab, value2.BaseValue) - value2.BaseValue;
			decimal num3;
			try
			{
				num3 = (includeMarkup ? (value2.NormalValues[checked(quality - 1)] + num2 * (1m + value2.MarkupRate)) : (num + num2));
			}
			catch (OverflowException)
			{
				return PriceResult.Unavailable("Valuation overflow.");
			}
			if (value.Routes.Count > 1)
			{
				PriceResult priceResult2 = ResolveExact(prefab, quality, path, depth, includeMarkup, skipUnavailableRoutes: true);
				if (priceResult2.Available && priceResult2.UnitValue <= num3)
				{
					return PriceResult.Priced(priceResult2.UnitValue, value2.Reason + " Bounded by known acquisition route: " + priceResult2.Reason + ". Other route unavailable: " + priceResult.Reason);
				}
			}
			return PriceResult.Priced(num3, value2.Reason + " Exact route unavailable: " + priceResult.Reason);
		}

		private PriceResult ResolveExact(string prefab, int quality, HashSet<string> path, int depth, bool includeMarkup, bool skipUnavailableRoutes = false)
		{
			if (!_items.TryGetValue(prefab, out ItemSpec item))
			{
				return PriceResult.Unavailable("Unknown prefab: " + prefab);
			}
			if (quality < 1 || quality > item.MaxQuality)
			{
				return PriceResult.Unavailable("Invalid quality.");
			}
			if (item.BlockReason != null)
			{
				return PriceResult.Unavailable(item.BlockReason);
			}
			string text = prefab + ":" + quality;
			if (depth > 64 || !path.Add(text))
			{
				return PriceResult.Unavailable("Recipe cycle/depth limit: " + text);
			}
			try
			{
				CraftRoute[] array = item.Routes.Where((CraftRoute r) => !r.IsValueCeilingOnly).ToArray();
				if (array.Length != 0)
				{
					decimal? num = null;
					ValuationBiome? valuationBiome = null;
					bool flag = array.Any((CraftRoute r) => _routeBiomes.ContainsKey(item.Prefab + "\n" + r.Id));
					ValuationBiome? valuationBiome2 = (flag ? new ValuationBiome?(array.Where((CraftRoute r) => _routeBiomes.ContainsKey(item.Prefab + "\n" + r.Id)).Min((CraftRoute r) => _routeBiomes[item.Prefab + "\n" + r.Id].Biome)) : ((ValuationBiome?)null));
					string text2 = "";
					CraftRoute[] array2 = array;
					foreach (CraftRoute craftRoute in array2)
					{
						_routeBiomes.TryGetValue(item.Prefab + "\n" + craftRoute.Id, out BiomeAdjustment value);
						if (flag && value == null)
						{
							if (!skipUnavailableRoutes)
							{
								return PriceResult.Unavailable(craftRoute.Id + ": original acquisition provenance is unresolved.");
							}
						}
						else
						{
							if (skipUnavailableRoutes && flag && value.Biome > valuationBiome2.Value)
							{
								continue;
							}
							RoutePrice routePrice = ResolveRoute(item, craftRoute, quality, path, depth, includeMarkup);
							if (!routePrice.Available)
							{
								if (!skipUnavailableRoutes)
								{
									return PriceResult.Unavailable(routePrice.Reason);
								}
							}
							else if (!num.HasValue || (flag && value.Biome < valuationBiome.Value) || ((!flag || value.Biome == valuationBiome.Value) && routePrice.Total < num.Value))
							{
								num = routePrice.Total;
								valuationBiome = value?.Biome;
								text2 = routePrice.Reason + (flag ? ("; earliest route access " + value.Name) : "");
							}
						}
					}
					return num.HasValue ? PriceResult.Priced(num.Value, "Recipe: " + text2) : PriceResult.Unavailable("No supported crafting route.");
				}
				if (quality != 1)
				{
					return PriceResult.Unavailable("No verified upgrade cost.");
				}
				return item.Anchor.HasValue ? PriceResult.Priced(OriginalBase(prefab, item.Anchor.Value), "Configured resource/special-drop anchor (with applicable tier minimum)") : PriceResult.Unavailable("No enabled anchor or supported recipe/conversion.");
			}
			catch (OverflowException)
			{
				return PriceResult.Unavailable("Valuation overflow.");
			}
			finally
			{
				path.Remove(text);
			}
		}

		private RoutePrice ResolveRoute(ItemSpec item, CraftRoute route, int quality, HashSet<string> path, int depth, bool includeMarkup)
		{
			if (route.UnsupportedReason != null)
			{
				return RoutePrice.Unavailable(route.Id + ": " + route.UnsupportedReason);
			}
			if (route.CostsByQuality.Count < quality)
			{
				return RoutePrice.Unavailable(route.Id + ": upgrade requirements not audited.");
			}
			if (quality > 1 && (route.OutputCount != 1 || item.MaxStack != 1))
			{
				return RoutePrice.Unavailable("Batch/stackable upgrading requires a dedicated adapter.");
			}
			try
			{
				decimal value = default(decimal);
				bool flag = false;
				for (int i = 0; i < quality; i++)
				{
					decimal num = default(decimal);
					foreach (Material item2 in route.CostsByQuality[i])
					{
						if (item2.Amount != 0)
						{
							PriceResult priceResult = Resolve(item2.Prefab, item2.Quality, path, depth + 1, includeMarkup: false);
							if (!priceResult.Available)
							{
								return RoutePrice.Unavailable(route.Id + " -> " + priceResult.Reason);
							}
							if (priceResult.Reason.StartsWith("Difficulty estimate", StringComparison.Ordinal) || priceResult.Reason.Contains("estimated ingredient base"))
							{
								flag = true;
							}
							num += priceResult.UnitValue * (decimal)item2.Amount;
						}
					}
					if (num <= 0m)
					{
						return RoutePrice.Unavailable(route.Id + ": free/unpriced craft or upgrade.");
					}
					value += ((i == 0) ? (item.LimitConversionValue ? (num / (decimal)route.OutputCount) : OriginalBase(item.Prefab, num / (decimal)route.OutputCount)) : num);
				}
				decimal num2 = (includeMarkup ? route.MarkupRate : 0m);
				if (num2 > 0m)
				{
					value *= 1m + num2;
				}
				return RoutePrice.Priced(value, route.Id + ((num2 == 0.20m) ? " (includes 20% crafting markup)" : ((num2 == 0.10m) ? " (includes 10% refining markup)" : "")) + (flag ? " (estimated ingredient base)" : ""));
			}
			catch (OverflowException)
			{
				return RoutePrice.Unavailable("Valuation overflow.");
			}
		}
	}
	public sealed class Material
	{
		public string Prefab { get; }

		public int Amount { get; }

		public int Quality { get; }

		public Material(string prefab, int amount)
			: this(prefab, amount, 1)
		{
		}

		public Material(string prefab, int amount, int quality)
		{
			if (string.IsNullOrWhiteSpace(prefab) || amount < 0 || quality < 1)
			{
				throw new ArgumentException("Invalid ingredient.");
			}
			Prefab = prefab;
			Amount = amount;
			Quality = quality;
		}
	}
	public sealed class CraftRoute
	{
		public string Id { get; }

		public int OutputCount { get; }

		public IReadOnlyList<IReadOnlyList<Material>> CostsByQuality { get; }

		public string? UnsupportedReason { get; }

		public decimal MarkupRate { get; }

		public bool IsValueCeilingOnly { get; }

		public CraftRoute(string id, int outputCount, IReadOnlyList<IReadOnlyList<Material>> costs, string? unsupportedReason = null)
			: this(id, outputCount, costs, unsupportedReason, 0m)
		{
		}

		public CraftRoute(string id, int outputCount, IReadOnlyList<IReadOnlyList<Material>> costs, string? unsupportedReason, decimal markupRate)
			: this(id, outputCount, costs, unsupportedReason, markupRate, isValueCeilingOnly: false)
		{
		}

		public CraftRoute(string id, int outputCount, IReadOnlyList<IReadOnlyList<Material>> costs, string? unsupportedReason, decimal markupRate, bool isValueCeilingOnly)
		{
			if (outputCount < 1)
			{
				throw new ArgumentOutOfRangeException("outputCount");
			}
			if (markupRate != 0m && markupRate != 0.10m && markupRate != 0.20m)
			{
				throw new ArgumentOutOfRangeException("markupRate", "Unsupported crafting/refining markup.");
			}
			Id = id;
			OutputCount = outputCount;
			CostsByQuality = costs;
			UnsupportedReason = unsupportedReason;
			MarkupRate = markupRate;
			IsValueCeilingOnly = isValueCeilingOnly;
		}
	}
	public sealed class ItemSpec
	{
		public string Prefab { get; }

		public int MaxQuality { get; }

		public int MaxStack { get; }

		public decimal? Anchor { get; }

		public IReadOnlyList<CraftRoute> Routes { get; }

		public string? BlockReason { get; }

		public bool IsTrophy { get; }

		public bool LimitConversionValue { get; }

		public ItemSpec(string prefab, int maxQuality, int maxStack, decimal? anchor, IReadOnlyList<CraftRoute>? routes = null, string? blockReason = null)
			: this(prefab, maxQuality, maxStack, anchor, routes, blockReason, isTrophy: false)
		{
		}

		public ItemSpec(string prefab, int maxQuality, int maxStack, decimal? anchor, IReadOnlyList<CraftRoute>? routes, string? blockReason, bool isTrophy)
			: this(prefab, maxQuality, maxStack, anchor, routes, blockReason, isTrophy, limitConversionValue: false)
		{
		}

		public ItemSpec(string prefab, int maxQuality, int maxStack, decimal? anchor, IReadOnlyList<CraftRoute>? routes, string? blockReason, bool isTrophy, bool limitConversionValue)
		{
			if (string.IsNullOrWhiteSpace(prefab) || maxQuality < 1 || maxStack < 1)
			{
				throw new ArgumentException("Invalid item metadata.");
			}
			if (anchor.HasValue && (anchor.Value <= 0m || anchor.Value > 1000000000000m))
			{
				throw new ArgumentOutOfRangeException("anchor");
			}
			Prefab = prefab;
			MaxQuality = maxQuality;
			MaxStack = maxStack;
			Anchor = anchor;
			Routes = routes ?? Array.Empty<CraftRoute>();
			BlockReason = blockReason;
			IsTrophy = isTrophy;
			LimitConversionValue = limitConversionValue;
		}
	}
	public sealed class PriceResult
	{
		public bool Available { get; }

		public decimal UnitValue { get; }

		public string Reason { get; }

		private PriceResult(bool available, decimal value, string reason)
		{
			Available = available;
			UnitValue = value;
			Reason = reason;
		}

		public static PriceResult Priced(decimal value, string reason)
		{
			if (!(value > 0m) || !(value <= 1000000000000m))
			{
				return Unavailable("Value is zero or outside the supported range.");
			}
			return new PriceResult(available: true, value, reason);
		}

		public static PriceResult Unavailable(string reason)
		{
			return new PriceResult(available: false, 0m, reason);
		}
	}
	public sealed class EconomyConfiguration
	{
		private sealed class StrictJsonGrammar
		{
			private readonly string _text;

			private int _at;

			private char Current
			{
				get
				{
					if (_at >= _text.Length)
					{
						return '\0';
					}
					return _text[_at];
				}
			}

			public StrictJsonGrammar(string text)
			{
				_text = text;
			}

			private void Fail()
			{
				throw new InvalidDataException("Malformed economy JSON near character " + _at.ToString(CultureInfo.InvariantCulture) + ".");
			}

			private static bool Digit(char value)
			{
				if (value >= '0')
				{
					return value <= '9';
				}
				return false;
			}

			private void White()
			{
				while (Current == ' ' || Current == '\t' || Current == '\r' || Current == '\n')
				{
					_at++;
				}
			}

			private void Take(char value)
			{
				if (Current != value || _at == _text.Length)
				{
					Fail();
				}
				_at++;
			}

			public void Validate()
			{
				White();
				Value(0);
				White();
				if (_at != _text.Length)
				{
					Fail();
				}
			}

			private void Value(int depth)
			{
				if (depth > 8)
				{
					Fail();
				}
				White();
				switch (Current)
				{
				case '{':
					_at++;
					White();
					if (Current == '}')
					{
						_at++;
						break;
					}
					while (true)
					{
						String();
						White();
						Take(':');
						Value(depth + 1);
						White();
						if (Current == '}')
						{
							break;
						}
						Take(',');
						White();
					}
					_at++;
					break;
				case '[':
					_at++;
					White();
					if (Current == ']')
					{
						_at++;
						break;
					}
					while (true)
					{
						Value(depth + 1);
						White();
						if (Current == ']')
						{
							break;
						}
						Take(',');
						White();
					}
					_at++;
					break;
				case '"':
					String();
					break;
				case 't':
					Literal("true");
					break;
				case 'f':
					Literal("false");
					break;
				case 'n':
					Literal("null");
					break;
				default:
					if (Current == '-' || Digit(Current))
					{
						Number();
					}
					else
					{
						Fail();
					}
					break;
				}
			}

			private void Literal(string value)
			{
				foreach (char value2 in value)
				{
					Take(value2);
				}
			}

			private void Number()
			{
				if (Current == '-')
				{
					_at++;
				}
				if (Current == '0')
				{
					_at++;
				}
				else
				{
					if (Current < '1' || Current > '9')
					{
						Fail();
					}
					while (Digit(Current))
					{
						_at++;
					}
				}
				if (Current == '.')
				{
					_at++;
					Digits();
				}
				if (Current == 'e' || Current == 'E')
				{
					_at++;
					if (Current == '+' || Current == '-')
					{
						_at++;
					}
					Digits();
				}
			}

			private void Digits()
			{
				if (!Digit(Current))
				{
					Fail();
				}
				while (Digit(Current))
				{
					_at++;
				}
			}

			private int HexCode()
			{
				int num = 0;
				for (int i = 0; i < 4; i++)
				{
					char current = Current;
					int num2 = ((current >= '0' && current <= '9') ? (current - 48) : ((current >= 'a' && current <= 'f') ? (current - 97 + 10) : ((current >= 'A' && current <= 'F') ? (current - 65 + 10) : (-1))));
					if (num2 < 0)
					{
						Fail();
					}
					_at++;
					num = num * 16 + num2;
				}
				return num;
			}

			private void String()
			{
				Take('"');
				while (true)
				{
					if (_at == _text.Length || Current < ' ')
					{
						Fail();
					}
					char current = Current;
					_at++;
					switch (current)
					{
					case '"':
						return;
					case '\\':
					{
						char current2 = Current;
						_at++;
						switch (current2)
						{
						case '"':
						case '/':
						case '\\':
						case 'b':
						case 'f':
						case 'n':
						case 'r':
						case 't':
							break;
						case 'u':
						{
							int num = HexCode();
							if (num >= 55296 && num <= 56319)
							{
								Take('\\');
								Take('u');
								int num2 = HexCode();
								if (num2 < 56320 || num2 > 57343)
								{
									Fail();
								}
							}
							else if (num >= 56320 && num <= 57343)
							{
								Fail();
							}
							break;
						}
						default:
							Fail();
							break;
						}
						continue;
					}
					}
					if (char.IsHighSurrogate(current))
					{
						if (!char.IsLowSurrogate(Current))
						{
							Fail();
						}
						_at++;
					}
					else if (char.IsLowSurrogate(current))
					{
						Fail();
					}
				}
			}
		}

		public const int MaxCharacters = 1048576;

		public const int MaxAnchors = 4096;

		public const decimal WoodBaseline = 0.3m;

		public const decimal IronBaseline = 120.6m;

		public const decimal TarBaseline = 25m;

		private static readonly HashSet<string> ReviewedAnchors = new HashSet<string>(StringComparer.Ordinal)
		{
			"Wood", "Stone", "Flint", "LeatherScraps", "DeerHide", "Feathers", "Resin", "FineWood", "RoundLog", "Iron",
			"BlackMetalScrap", "CopperOre", "CopperScrap", "TinOre", "IronOre", "IronScrap", "BronzeScrap", "SilverOre", "FlametalOreNew", "GoldOre",
			"Sap", "Softtissue", "Tar"
		};

		private static readonly Lazy<IReadOnlyDictionary<string, decimal>> DefaultAnchors = new Lazy<IReadOnlyDictionary<string, decimal>>(delegate
		{
			using Stream stream = typeof(EconomyConfiguration).Assembly.GetManifestResourceStream("FateForge.default-economy.json") ?? throw new InvalidDataException("Embedded reviewed economy is missing.");
			using StreamReader streamReader = new StreamReader(stream);
			return Parse(streamReader.ReadToEnd(), allowLegacyMigration: false).ActiveAnchors;
		});

		public string Profile { get; }

		public int RowCount { get; }

		public IReadOnlyDictionary<string, decimal> ActiveAnchors { get; }

		public IReadOnlyList<string> UnreviewedEnabled { get; }

		public IReadOnlyList<string> DisabledReviewedAnchors { get; }

		public bool LegacyDefaultsSupplemented { get; }

		private static bool IsReviewed(string prefab)
		{
			if (!ReviewedAnchors.Contains(prefab) && !MouldValuation.IsCalibratedPrefab(prefab) && !EpicMaterialValuation.IsReviewedPrefab(prefab))
			{
				return ReviewedAcquisitionValuation.IsReviewedPrefab(prefab);
			}
			return true;
		}

		public IReadOnlyList<string> CustomizedAnchors()
		{
			return CustomizedAnchorIds(ActiveAnchors);
		}

		public static IReadOnlyList<string> CustomizedAnchorIds(IReadOnlyDictionary<string, decimal> anchors)
		{
			decimal value;
			return (from x in anchors
				where !DefaultAnchors.Value.TryGetValue(x.Key, out value) || x.Value != value
				select x.Key).ToArray();
		}

		private EconomyConfiguration(string profile, int rowCount, Dictionary<string, decimal> anchors, List<string> unreviewed, List<string> disabled, bool supplemented)
		{
			Profile = profile;
			RowCount = rowCount;
			ActiveAnchors = anchors;
			UnreviewedEnabled = unreviewed;
			DisabledReviewedAnchors = disabled;
			LegacyDefaultsSupplemented = supplemented;
		}

		public static EconomyConfiguration Parse(string json)
		{
			return Parse(json, allowLegacyMigration: true);
		}

		private static EconomyConfiguration Parse(string json, bool allowLegacyMigration)
		{
			if (string.IsNullOrWhiteSpace(json) || json.Length > 1048576)
			{
				throw new InvalidDataException("Economy configuration is empty or exceeds the supported size.");
			}
			if (json[0] == '\ufeff')
			{
				json = json.Substring(1);
			}
			try
			{
				new StrictJsonGrammar(json).Validate();
				XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas
				{
					MaxDepth = 8,
					MaxArrayLength = 4096,
					MaxStringContentLength = 65536,
					MaxBytesPerRead = 4096,
					MaxNameTableCharCount = 16384
				};
				using XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), quotas);
				Dictionary<string, XElement> dictionary = ObjectFields(XDocument.Load(reader).Root ?? throw new InvalidDataException("Economy JSON has no root object."), "economy", "schema", "profile", "anchors");
				XElement xElement = Required(dictionary, "schema", "economy");
				if (TypeOf(xElement) != "number" || xElement.Value != "1")
				{
					throw new InvalidDataException("Economy schema must be the integer 1.");
				}
				XElement value;
				string profile = (dictionary.TryGetValue("profile", out value) ? StringValue(value, "profile") : "");
				XElement xElement2 = Required(dictionary, "anchors", "economy");
				if (TypeOf(xElement2) != "array")
				{
					throw new InvalidDataException("Economy anchors must be a JSON array.");
				}
				XElement[] array = xElement2.Elements().ToArray();
				if (array.Length > 4096)
				{
					throw new InvalidDataException("Too many economy anchors.");
				}
				bool flag = IsPristineLegacyDefault(json);
				Dictionary<string, decimal> dictionary2 = new Dictionary<string, decimal>(StringComparer.Ordinal);
				List<string> list = new List<string>();
				List<string> list2 = new List<string>();
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				XElement[] array2 = array;
				for (int i = 0; i < array2.Length; i++)
				{
					Dictionary<string, XElement> dictionary3 = ObjectFields(array2[i], "anchor", "prefab", "value", "enabled", "note");
					string text = StringValue(Required(dictionary3, "prefab", "anchor"), "prefab");
					if (string.IsNullOrWhiteSpace(text) || text.Length > 256 || !hashSet.Add(text))
					{
						throw new InvalidDataException("Empty, oversized or duplicate anchor identifier.");
					}
					string text2 = StringValue(Required(dictionary3, "value", "anchor"), "value");
					if (text2.Length > 128 || !decimal.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result <= 0m || result > 1000000000000m)
					{
						throw new InvalidDataException("Invalid price for " + text + ".");
					}
					XElement xElement3 = Required(dictionary3, "enabled", "anchor");
					if (TypeOf(xElement3) != "boolean" || (xElement3.Value != "true" && xElement3.Value != "false"))
					{
						throw new InvalidDataException("Anchor enabled must be a JSON boolean.");
					}
					if (dictionary3.TryGetValue("note", out XElement value2))
					{
						StringValue(value2, "note");
					}
					if (xElement3.Value != "true")
					{
						if (IsReviewed(text))
						{
							list2.Add(text);
						}
						continue;
					}
					if (!IsReviewed(text))
					{
						list.Add(text);
						continue;
					}
					if (text == "Wood" && result != 0.3m && (!flag || !(result == 1m)))
					{
						throw new InvalidDataException("Fixed baseline required: Wood = 0.3 per item. Update customized old profiles explicitly.");
					}
					dictionary2.Add(text, result);
				}
				if (flag && !allowLegacyMigration)
				{
					throw new InvalidDataException("Embedded reviewed economy is an obsolete default; refusing recursive migration.");
				}
				if (flag)
				{
					using Stream stream = typeof(EconomyConfiguration).Assembly.GetManifestResourceStream("FateForge.default-economy.json") ?? throw new InvalidDataException("Embedded reviewed economy is missing.");
					using StreamReader streamReader = new StreamReader(stream);
					EconomyConfiguration economyConfiguration = Parse(streamReader.ReadToEnd(), allowLegacyMigration: false);
					dictionary2 = economyConfiguration.ActiveAnchors.ToDictionary<KeyValuePair<string, decimal>, string, decimal>((KeyValuePair<string, decimal> x) => x.Key, (KeyValuePair<string, decimal> x) => x.Value, StringComparer.Ordinal);
					list2 = economyConfiguration.DisabledReviewedAnchors.ToList();
					list = economyConfiguration.UnreviewedEnabled.ToList();
					profile = economyConfiguration.Profile;
				}
				return new EconomyConfiguration(profile, array.Length, dictionary2, list, list2, flag);
			}
			catch (InvalidDataException)
			{
				throw;
			}
			catch (Exception ex2) when (ex2 is XmlException || ex2 is FormatException || ex2 is ArgumentException || ex2 is SerializationException)
			{
				throw new InvalidDataException("Malformed economy JSON: " + ex2.GetBaseException().Message, ex2);
			}
		}

		private static bool IsPristineLegacyDefault(string json)
		{
			string s = json.TrimStart('\ufeff').Replace("\r\n", "\n");
			using SHA256 sHA = SHA256.Create();
			string text = BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(s))).Replace("-", "").ToLowerInvariant();
			int result;
			switch (text)
			{
			default:
				result = ((text == "07ed79f168c2c7384547405189e3f35d02329e600d808a532359a2dd7668ffbb") ? 1 : 0);
				break;
			case "0ead73d416a52cfd3436758c1543bffa6831df80f2856f57f95c953b169ab1a3":
			case "0311eb297586527391d4ea2438832259ce687879e3c834193419674781182c75":
			case "100365f05010194ece0a74be40224e90a75e7a72ce4311681be59a1c3472ded2":
			case "5e2ebc5f902f84ba57a79a853dc3972c8c2c9ad3e2a5e52e39414d87b3f73cd3":
				result = 1;
				break;
			}
			return (byte)result != 0;
		}

		private static string TypeOf(XElement element)
		{
			return ((string?)element.Attribute("type")) ?? "";
		}

		private static Dictionary<string, XElement> ObjectFields(XElement element, string location, params string[] allowed)
		{
			if (TypeOf(element) != "object")
			{
				throw new InvalidDataException(location + " must be a JSON object.");
			}
			Dictionary<string, XElement> dictionary = new Dictionary<string, XElement>(StringComparer.Ordinal);
			foreach (XElement item in element.Elements())
			{
				string localName = item.Name.LocalName;
				if (item.Name.NamespaceName != "" || !allowed.Contains<string>(localName, StringComparer.Ordinal))
				{
					throw new InvalidDataException("Unknown " + location + " property: " + localName + ".");
				}
				if (dictionary.ContainsKey(localName))
				{
					throw new InvalidDataException("Duplicate " + location + " property: " + localName + ".");
				}
				dictionary.Add(localName, item);
			}
			return dictionary;
		}

		private static XElement Required(Dictionary<string, XElement> fields, string name, string location)
		{
			if (!fields.TryGetValue(name, out XElement value))
			{
				throw new InvalidDataException("Missing " + location + " property: " + name + ".");
			}
			return value;
		}

		private static string StringValue(XElement element, string name)
		{
			if (TypeOf(element) != "string" || element.HasElements)
			{
				throw new InvalidDataException("Economy " + name + " must be a JSON string.");
			}
			return element.Value;
		}
	}
	public static class EpicGearValuation
	{
		public const int RarityCount = 6;

		private static readonly decimal[] Multipliers = new decimal[6] { 1.2m, 1.5m, 2m, 3m, 5m, 8m };

		private static readonly string[] Names = new string[6] { "Magic", "Rare", "Epic", "Legendary", "Mythic", "Ancient" };

		public static decimal Multiplier(int rarity)
		{
			if (rarity < 0 || rarity >= 6)
			{
				throw new ArgumentOutOfRangeException("rarity");
			}
			return Multipliers[rarity];
		}

		public static PriceResult Apply(PriceResult normalPrice, int rarity)
		{
			if (!normalPrice.Available)
			{
				return normalPrice;
			}
			if (rarity < 0 || rarity >= 6)
			{
				return PriceResult.Unavailable("Unsupported Epic Loot gear rarity.");
			}
			decimal num = Multipliers[rarity];
			if (normalPrice.UnitValue <= 0m || normalPrice.UnitValue > 1000000000000m / num)
			{
				return PriceResult.Unavailable("Epic Loot gear rarity value exceeds supported limits.");
			}
			return PriceResult.Priced(normalPrice.UnitValue * num, normalPrice.Reason + " Final Epic Loot gear rarity " + Names[rarity] + " ×" + num.ToString("0.##", CultureInfo.InvariantCulture) + " applied once after ordinary/enchant costs and the output biome. Individual effects and sockets add no guessed premium.");
		}
	}
	[DataContract]
	public sealed class EpicLootCost
	{
		[DataMember(IsRequired = true)]
		public string Item = "";

		[DataMember(IsRequired = true)]
		public int Amount;
	}
	[DataContract]
	public sealed class EpicLootEffect
	{
		[DataMember]
		public string EffectType = "";

		[DataMember]
		public double EffectValue;
	}
	[DataContract]
	public sealed class EpicLootSocket
	{
		[DataMember]
		public string SourcePrefab = "";

		[DataMember]
		public EpicLootEffect? Effect;
	}
	[DataContract]
	public sealed class EpicLootMagicData
	{
		[DataMember(IsRequired = true)]
		public int Version;

		[DataMember(IsRequired = true)]
		public int Rarity;

		[DataMember]
		public bool IsUnidentified;

		[DataMember]
		public string DisplayName = "";

		[DataMember]
		public string LegendaryID = "";

		[DataMember]
		public string SetID = "";

		[DataMember]
		public int SocketCount;

		[DataMember]
		public EpicLootEffect[] Effects = Array.Empty<EpicLootEffect>();

		[DataMember]
		public EpicLootSocket[] Sockets = Array.Empty<EpicLootSocket>();

		[DataMember]
		public int[] AugmentedEffectIndices = Array.Empty<int>();

		[DataMember]
		public int[] TemperedEffectIndices = Array.Empty<int>();

		[DataMember]
		public int AugmentedEffectIndex = -1;

		[OnDeserializing]
		private void InitializeDefaults(StreamingContext context)
		{
			AugmentedEffectIndex = -1;
		}
	}
	public static class EpicLootMetadata
	{
		public const string EconomicGate = "Enchanted reward generation is not implemented. Existing enchanted offerings use base equipment and configured enchant-material costs; effects, sockets and upgrades to magic effects receive no invented bonus.";

		public const int MaxJsonLength = 65536;

		public const string EmptyMagicComponentKey = "randyknapp.mods.epicloot#EpicLoot.MagicItemComponent";

		public static bool HasUnreviewedCustomData(IEnumerable<KeyValuePair<string, string>> customData, bool ordinaryConfirmedBySupportedApi)
		{
			return customData.Any<KeyValuePair<string, string>>((KeyValuePair<string, string> x) => !ordinaryConfirmedBySupportedApi || x.Key != "randyknapp.mods.epicloot#EpicLoot.MagicItemComponent" || x.Value != "");
		}

		private static T Read<T>(string json)
		{
			if (string.IsNullOrWhiteSpace(json) || json.Length > 65536)
			{
				throw new InvalidDataException("Missing or oversized Epic Loot metadata.");
			}
			using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
			return (T)(new DataContractJsonSerializer(typeof(T)).ReadObject(stream) ?? throw new InvalidDataException("Null Epic Loot metadata."));
		}

		public static EpicLootMagicData ParseMagic(string json)
		{
			EpicLootMagicData epicLootMagicData = Read<EpicLootMagicData>(json);
			if (epicLootMagicData.Version != 3 || epicLootMagicData.Rarity < 0 || epicLootMagicData.Rarity > 100 || epicLootMagicData.SocketCount < 0 || epicLootMagicData.SocketCount > 100)
			{
				throw new InvalidDataException("Unsupported Epic Loot magic metadata version or bounds.");
			}
			epicLootMagicData.Effects = epicLootMagicData.Effects ?? Array.Empty<EpicLootEffect>();
			epicLootMagicData.Sockets = epicLootMagicData.Sockets ?? Array.Empty<EpicLootSocket>();
			if (epicLootMagicData.Effects.Length > 100 || epicLootMagicData.Sockets.Length > epicLootMagicData.SocketCount || epicLootMagicData.Sockets.Any((EpicLootSocket x) => x == null))
			{
				throw new InvalidDataException("Invalid Epic Loot effects or sockets.");
			}
			foreach (EpicLootEffect item in epicLootMagicData.Effects.Concat<EpicLootEffect>(from x in epicLootMagicData.Sockets
				where x.Effect != null
				select x.Effect))
			{
				if (item == null || string.IsNullOrWhiteSpace(item.EffectType) || double.IsNaN(item.EffectValue) || double.IsInfinity(item.EffectValue))
				{
					throw new InvalidDataException("Invalid Epic Loot effect.");
				}
			}
			return epicLootMagicData;
		}

		public static IReadOnlyList<EpicLootCost> ParseCosts(string json)
		{
			EpicLootCost[] array = Read<EpicLootCost[]>(json);
			if (array.Length > 100)
			{
				throw new InvalidDataException("Too many Epic Loot cost entries.");
			}
			EpicLootCost[] array2 = array;
			foreach (EpicLootCost epicLootCost in array2)
			{
				if (epicLootCost == null || string.IsNullOrWhiteSpace(epicLootCost.Item) || epicLootCost.Item.Length > 256 || epicLootCost.Amount < 1 || epicLootCost.Amount > 10000)
				{
					throw new InvalidDataException("Invalid Epic Loot item cost.");
				}
			}
			return array;
		}

		public static string DescribeCosts(IReadOnlyList<EpicLootCost> costs)
		{
			if (costs.Count != 0)
			{
				return string.Join(", ", costs.Select((EpicLootCost x) => x.Amount.ToString(CultureInfo.InvariantCulture) + " " + x.Item));
			}
			return "No configured cost (unverified).";
		}

		public static string DescribeMagic(EpicLootMagicData data)
		{
			if (data.IsUnidentified)
			{
				return "Unidentified magic item; effects are hidden.";
			}
			List<string> list = new List<string>();
			list.AddRange(data.Effects.Select((EpicLootEffect x) => x.EffectType + " " + x.EffectValue.ToString("G9", CultureInfo.InvariantCulture)));
			list.Add("Sockets " + data.Sockets.Length + "/" + data.SocketCount);
			list.AddRange(data.Sockets.Select((EpicLootSocket x) => x.SourcePrefab + ((x.Effect == null) ? " (inert)" : (": " + x.Effect.EffectType + " " + x.Effect.EffectValue.ToString("G9", CultureInfo.InvariantCulture)))));
			if (!string.IsNullOrEmpty(data.LegendaryID))
			{
				list.Add("Legendary " + data.LegendaryID);
			}
			if (!string.IsNullOrEmpty(data.SetID))
			{
				list.Add("Set " + data.SetID);
			}
			if (data.AugmentedEffectIndex < 0)
			{
				int[] augmentedEffectIndices = data.AugmentedEffectIndices;
				if (((augmentedEffectIndices != null && augmentedEffectIndices.Length != 0) ? 1 : 0) <= (false ? 1 : 0))
				{
					goto IL_0110;
				}
			}
			list.Add("Augmented");
			goto IL_0110;
			IL_0110:
			int[] temperedEffectIndices = data.TemperedEffectIndices;
			if (((temperedEffectIndices != null && temperedEffectIndices.Length != 0) ? 1 : 0) > (false ? 1 : 0))
			{
				list.Add("Tempered");
			}
			return string.Join("; ", list);
		}

		public static PriceResult EnchantPrice(Economy economy, string prefab, int quality, IReadOnlyList<EpicLootCost> costs)
		{
			PriceResult priceResult = economy.BasePrice(prefab, quality);
			if (!priceResult.Available)
			{
				return PriceResult.Unavailable("Base equipment: " + priceResult.Reason);
			}
			if (costs == null || costs.Count == 0 || costs.Count > 256)
			{
				return PriceResult.Unavailable("Epic Loot has no bounded configured enchant cost for this selection.");
			}
			try
			{
				decimal unitValue = priceResult.UnitValue;
				foreach (EpicLootCost cost in costs)
				{
					if (cost == null || string.IsNullOrWhiteSpace(cost.Item) || cost.Item.Length > 256 || cost.Amount < 1 || cost.Amount > 10000)
					{
						return PriceResult.Unavailable("Invalid Epic Loot item cost.");
					}
					PriceResult priceResult2 = economy.BasePrice(cost.Item);
					if (!priceResult2.Available)
					{
						return PriceResult.Unavailable("Unpriced enchant material " + cost.Item + ": " + priceResult2.Reason);
					}
					unitValue += priceResult2.UnitValue * (decimal)cost.Amount;
				}
				return economy.ApplyFinalAdjustments(prefab, PriceResult.Priced(unitValue * 1.20m, "Equipment base plus configured enchant materials; one 20% crafting premium. Effects and sockets add no estimated bonus."));
			}
			catch (OverflowException)
			{
				return PriceResult.Unavailable("Enchant value exceeds the supported range.");
			}
		}

		public static string Fingerprint(string magicJson, string enchantCostsJson, string sacrificeProductsJson, IEnumerable<KeyValuePair<string, string>> customData)
		{
			StringBuilder value = new StringBuilder();
			Add(magicJson);
			Add(enchantCostsJson);
			Add(sacrificeProductsJson);
			foreach (KeyValuePair<string, string> item in customData.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key, StringComparer.Ordinal))
			{
				Add(item.Key);
				Add(item.Value);
			}
			using (SHA256 sHA = SHA256.Create())
			{
				return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(value.ToString()))).Replace("-", "").ToLowerInvariant();
			}
			void Add(string? field)
			{
				field = field ?? "";
				value.Append(field.Length).Append(':').Append(field);
			}
		}
	}
	public sealed class EpicMaterialConversion
	{
		public string Product { get; }

		public int Amount { get; }

		public IReadOnlyList<Material> Resources { get; }

		public EpicMaterialConversion(string product, int amount, IEnumerable<Material> resources)
		{
			if (string.IsNullOrWhiteSpace(product) || amount < 1 || amount > 10000)
			{
				throw new ArgumentException("Invalid Epic Loot conversion.");
			}
			Product = product;
			Amount = amount;
			Resources = resources.ToArray();
			if (Resources.Count == 0 || Resources.Count > 100 || Resources.Any((Material x) => x.Amount < 1 || x.Amount > 10000))
			{
				throw new ArgumentException("Invalid Epic Loot conversion ingredients.");
			}
		}
	}
	public static class EpicMaterialValuation
	{
		public const decimal MagicSeed = 300m;

		public const string ShardTemplatePrefab = "_ShardStone";

		private static readonly HashSet<string> Reviewed = new HashSet<string>(StringComparer.Ordinal)
		{
			"Andvaranaut", "Black_Ancient_ShardStone", "Black_Epic_ShardStone", "Black_Legendary_ShardStone", "Black_Magic_ShardStone", "Black_Mythic_ShardStone", "Black_Rare_ShardStone", "Bonemass_Ancient_ShardStone", "Bonemass_Epic_ShardStone", "Bonemass_Legendary_ShardStone",
			"Bonemass_Mythic_ShardStone", "Cyan_Ancient_ShardStone", "Cyan_Epic_ShardStone", "Cyan_Legendary_ShardStone", "Cyan_Magic_ShardStone", "Cyan_Mythic_ShardStone", "Cyan_Rare_ShardStone", "DarkBlue_Ancient_ShardStone", "DarkBlue_Epic_ShardStone", "DarkBlue_Legendary_ShardStone",
			"DarkBlue_Magic_ShardStone", "DarkBlue_Mythic_ShardStone", "DarkBlue_Rare_ShardStone", "DarkGreen_Ancient_ShardStone", "DarkGreen_Epic_ShardStone", "DarkGreen_Legendary_ShardStone", "DarkGreen_Magic_ShardStone", "DarkGreen_Mythic_ShardStone", "DarkGreen_Rare_ShardStone", "DarkPurple_Ancient_ShardStone",
			"DarkPurple_Epic_ShardStone", "DarkPurple_Legendary_ShardStone", "DarkPurple_Magic_ShardStone", "DarkPurple_Mythic_ShardStone", "DarkPurple_Rare_ShardStone", "DarkRed_Ancient_ShardStone", "DarkRed_Epic_ShardStone", "DarkRed_Legendary_ShardStone", "DarkRed_Magic_ShardStone", "DarkRed_Mythic_ShardStone",
			"DarkRed_Rare_ShardStone", "DustAncient", "DustEpic", "DustLegendary", "DustMagic", "DustMythic", "DustRare", "Eikthyr_Ancient_ShardStone", "Eikthyr_Epic_ShardStone", "Eikthyr_Legendary_ShardStone",
			"Eikthyr_Mythic_ShardStone", "Eikthyr_Rare_ShardStone", "Elder_Ancient_ShardStone", "Elder_Epic_ShardStone", "Elder_Legendary_ShardStone", "Elder_Mythic_ShardStone", "Elder_Rare_ShardStone", "EssenceAncient", "EssenceEpic", "EssenceLegendary",
			"EssenceMagic", "EssenceMythic", "EssenceRare", "Fader_Ancient_ShardStone", "Fader_Mythic_ShardStone", "Firewalker_Ancient_ShardStone", "Firewalker_Epic_ShardStone", "Firewalker_Legendary_ShardStone", "Firewalker_Mythic_ShardStone", "Golden_Ancient_ShardStone",
			"Golden_Epic_ShardStone", "Golden_Legendary_ShardStone", "Golden_Magic_ShardStone", "Golden_Mythic_ShardStone", "Golden_Rare_ShardStone", "Green_Ancient_ShardStone", "Green_Epic_ShardStone", "Green_Legendary_ShardStone", "Green_Magic_ShardStone", "Green_Mythic_ShardStone",
			"Green_Rare_ShardStone", "Grey_Ancient_ShardStone", "Grey_Epic_ShardStone", "Grey_Legendary_ShardStone", "Grey_Magic_ShardStone", "Grey_Mythic_ShardStone", "Grey_Rare_ShardStone", "LightBlue_Ancient_ShardStone", "LightBlue_Epic_ShardStone", "LightBlue_Legendary_ShardStone",
			"LightBlue_Magic_ShardStone", "LightBlue_Mythic_ShardStone", "LightBlue_Rare_ShardStone", "LightGreen_Ancient_ShardStone", "LightGreen_Epic_ShardStone", "LightGreen_Legendary_ShardStone", "LightGreen_Magic_ShardStone", "LightGreen_Mythic_ShardStone", "LightGreen_Rare_ShardStone", "Moder_Ancient_ShardStone",
			"Moder_Epic_ShardStone", "Moder_Legendary_ShardStone", "Moder_Mythic_ShardStone", "Orange_Ancient_ShardStone", "Orange_Epic_ShardStone", "Orange_Legendary_ShardStone", "Orange_Magic_ShardStone", "Orange_Mythic_ShardStone", "Orange_Rare_ShardStone", "Peach_Ancient_ShardStone",
			"Peach_Epic_ShardStone", "Peach_Legendary_ShardStone", "Peach_Magic_ShardStone", "Peach_Mythic_ShardStone", "Peach_Rare_ShardStone", "Pink_Ancient_ShardStone", "Pink_Epic_ShardStone", "Pink_Legendary_ShardStone", "Pink_Magic_ShardStone", "Pink_Mythic_ShardStone",
			"Pink_Rare_ShardStone", "Purple_Ancient_ShardStone", "Purple_Epic_ShardStone", "Purple_Legendary_ShardStone", "Purple_Magic_ShardStone", "Purple_Mythic_ShardStone", "Purple_Rare_ShardStone", "Queen_Ancient_ShardStone", "Queen_Legendary_ShardStone", "Queen_Mythic_ShardStone",
			"ReagentAncient", "ReagentEpic", "ReagentLegendary", "ReagentMagic", "ReagentMythic", "ReagentRare", "Red_Ancient_ShardStone", "Red_Epic_ShardStone", "Red_Legendary_ShardStone", "Red_Magic_ShardStone",
			"Red_Mythic_ShardStone", "Red_Rare_ShardStone", "RunestoneAncient", "RunestoneEpic", "RunestoneLegendary", "RunestoneMagic", "RunestoneMythic", "RunestoneRare", "ShardAncient", "ShardEpic",
			"ShardLegendary", "ShardMagic", "ShardMythic", "ShardRare", "Stormcaller_Ancient_ShardStone", "Stormcaller_Epic_ShardStone", "Stormcaller_Legendary_ShardStone", "Stormcaller_Mythic_ShardStone", "White_Ancient_ShardStone", "White_Epic_ShardStone",
			"White_Legendary_ShardStone", "White_Magic_ShardStone", "White_Mythic_ShardStone", "White_Rare_ShardStone", "Yagluth_Ancient_ShardStone", "Yagluth_Legendary_ShardStone", "Yagluth_Mythic_ShardStone", "Yellow_Ancient_ShardStone", "Yellow_Epic_ShardStone", "Yellow_Legendary_ShardStone",
			"Yellow_Magic_ShardStone", "Yellow_Mythic_ShardStone", "Yellow_Rare_ShardStone", "_ShardStone", "AshLands_Ancient_Unidentified", "AshLands_Epic_Unidentified", "AshLands_Legendary_Unidentified", "AshLands_Magic_Unidentified", "AshLands_Mythic_Unidentified", "AshLands_Rare_Unidentified",
			"BlackForest_Ancient_Unidentified", "BlackForest_Epic_Unidentified", "BlackForest_Legendary_Unidentified", "BlackForest_Magic_Unidentified", "BlackForest_Mythic_Unidentified", "BlackForest_Rare_Unidentified", "DeepNorth_Ancient_Unidentified", "DeepNorth_Epic_Unidentified", "DeepNorth_Legendary_Unidentified", "DeepNorth_Magic_Unidentified",
			"DeepNorth_Mythic_Unidentified", "DeepNorth_Rare_Unidentified", "EtchedRunestoneAncient", "EtchedRunestoneEpic", "EtchedRunestoneLegendary", "EtchedRunestoneMagic", "EtchedRunestoneMythic", "EtchedRunestoneRare", "ForestToken", "GoldBountyToken",
			"GoldRubyRing", "IronBountyToken", "Land_Ancient_Unidentified", "Land_Epic_Unidentified", "Land_Legendary_Unidentified", "Land_Magic_Unidentified", "Land_Mythic_Unidentified", "Land_Rare_Unidentified", "LeatherBelt", "Meadows_Ancient_Unidentified",
			"Meadows_Epic_Unidentified", "Meadows_Legendary_Unidentified", "Meadows_Magic_Unidentified", "Meadows_Mythic_Unidentified", "Meadows_Rare_Unidentified", "Mistlands_Ancient_Unidentified", "Mistlands_Epic_Unidentified", "Mistlands_Legendary_Unidentified", "Mistlands_Magic_Unidentified", "Mistlands_Mythic_Unidentified",
			"Mistlands_Rare_Unidentified", "Mountain_Ancient_Unidentified", "Mountain_Epic_Unidentified", "Mountain_Legendary_Unidentified", "Mountain_Magic_Unidentified", "Mountain_Mythic_Unidentified", "Mountain_Rare_Unidentified", "Ocean_Ancient_Unidentified", "Ocean_Epic_Unidentified", "Ocean_Legendary_Unidentified",
			"Ocean_Magic_Unidentified", "Ocean_Mythic_Unidentified", "Ocean_Rare_Unidentified", "Plains_Ancient_Unidentified", "Plains_Epic_Unidentified", "Plains_Legendary_Unidentified", "Plains_Magic_Unidentified", "Plains_Mythic_Unidentified", "Plains_Rare_Unidentified", "ShardSlotChiselAncient",
			"ShardSlotChiselLegendary", "ShardSlotChiselMythic", "SilverRing", "Swamp_Ancient_Unidentified", "Swamp_Epic_Unidentified", "Swamp_Legendary_Unidentified", "Swamp_Magic_Unidentified", "Swamp_Mythic_Unidentified", "Swamp_Rare_Unidentified", "_Unidentified"
		};

		public static DifficultyEstimate ShardTemplateEstimate(IEnumerable<DifficultyEstimate> actualShards)
		{
			decimal num = actualShards.Select((DifficultyEstimate x) => x.BaseValue).DefaultIfEmpty(1800m).Min();
			return new DifficultyEstimate(new decimal[1] { num }, 0m, "Difficulty estimate: registered Epic Loot generic shard template. Uses the least expensive actual shard's raw family value; if no coloured definitions loaded, the reviewed six-Magic-material calibration. No colour, rarity, usable shard effect, acquisition route or sacrifice yield is verified for the generic template itself.");
		}

		public static decimal ScarcitySeed(decimal configuredCost, decimal magicReferenceCost)
		{
			if (configuredCost <= 0m || magicReferenceCost <= 0m)
			{
				throw new ArgumentOutOfRangeException();
			}
			return Math.Min(100000000000m, 300m * configuredCost / magicReferenceCost);
		}

		public static IReadOnlyDictionary<string, DifficultyEstimate> Calculate(IReadOnlyDictionary<string, int> rarities, IReadOnlyDictionary<string, decimal> seeds, IEnumerable<EpicMaterialConversion> conversions, IReadOnlyDictionary<string, decimal> externalBases, IReadOnlyDictionary<string, decimal>? overrides = null, IReadOnlyDictionary<string, decimal>? minimums = null)
		{
			if (rarities.Count > 4096 || seeds.Count > 4096)
			{
				throw new ArgumentException("Oversized Epic Loot valuation graph.");
			}
			EpicMaterialConversion[] array = conversions.Take(8193).ToArray();
			if (array.Length > 8192)
			{
				throw new ArgumentException("Oversized Epic Loot conversion graph.");
			}
			Dictionary<string, decimal> dictionary = new Dictionary<string, decimal>(StringComparer.Ordinal);
			Dictionary<string, decimal> normal = new Dictionary<string, decimal>(StringComparer.Ordinal);
			if (minimums != null && minimums.Values.Any((decimal x) => x <= 0m || x > 1000000000000m))
			{
				throw new ArgumentOutOfRangeException("minimums");
			}
			foreach (KeyValuePair<string, decimal> seed in seeds)
			{
				if (rarities.ContainsKey(seed.Key) && seed.Value > 0m && seed.Value <= 100000000000m)
				{
					string key = seed.Key;
					decimal value = (normal[seed.Key] = Minimum(seed.Key, seed.Value));
					dictionary[key] = value;
				}
			}
			foreach (KeyValuePair<string, decimal> item in overrides ?? new Dictionary<string, decimal>())
			{
				if (rarities.ContainsKey(item.Key))
				{
					string key2 = item.Key;
					decimal value = (normal[item.Key] = Minimum(item.Key, item.Value));
					dictionary[key2] = value;
				}
			}
			array = array.Where((EpicMaterialConversion row) => rarities.TryGetValue(row.Product, out var rarity) && row.Resources.All((Material x) => !rarities.TryGetValue(x.Prefab, out var value5) || value5 <= rarity) && (row.Resources.Any((Material x) => rarities.TryGetValue(x.Prefab, out value5) && value5 < rarity) || row.Resources.Where((Material x) => rarities.ContainsKey(x.Prefab)).Sum((Func<Material, long>)((Material x) => x.Amount)) > row.Amount || row.Resources.All((Material x) => !rarities.ContainsKey(x.Prefab)))).ToArray();
			for (int num3 = 0; num3 <= rarities.Count; num3++)
			{
				bool flag = false;
				EpicMaterialConversion[] array2 = array;
				foreach (EpicMaterialConversion epicMaterialConversion in array2)
				{
					if (overrides != null && overrides.ContainsKey(epicMaterialConversion.Product))
					{
						continue;
					}
					decimal num5 = default(decimal);
					bool flag2 = true;
					foreach (Material resource in epicMaterialConversion.Resources)
					{
						if (!dictionary.TryGetValue(resource.Prefab, out var value2) && !externalBases.TryGetValue(resource.Prefab, out value2))
						{
							flag2 = false;
							break;
						}
						if (value2 <= 0m || value2 > 1000000000000m / (decimal)resource.Amount)
						{
							flag2 = false;
							break;
						}
						num5 += value2 * (decimal)resource.Amount;
						if (num5 > 1000000000000m)
						{
							flag2 = false;
							break;
						}
					}
					if (!flag2 || num5 <= 0m)
					{
						continue;
					}
					decimal num6 = Minimum(epicMaterialConversion.Product, num5 / (decimal)epicMaterialConversion.Amount);
					if (!(num6 > 100000000000m))
					{
						if (!dictionary.TryGetValue(epicMaterialConversion.Product, out var value3) || num6 < value3)
						{
							dictionary[epicMaterialConversion.Product] = num6;
							flag = true;
						}
						decimal num7 = num6 * 1.10m;
						if (!normal.TryGetValue(epicMaterialConversion.Product, out var value4) || num7 < value4)
						{
							normal[epicMaterialConversion.Product] = num7;
							flag = true;
						}
					}
				}
				if (!flag)
				{
					break;
				}
			}
			return dictionary.ToDictionary<KeyValuePair<string, decimal>, string, DifficultyEstimate>((KeyValuePair<string, decimal> x) => x.Key, (KeyValuePair<string, decimal> x) => new DifficultyEstimate(new decimal[1] { x.Value }, 0m, "Difficulty estimate: Epic Loot active-config scarcity: Magic raw300 calibration; configured relative stash/token/sacrifice yields and material conversions. Merchant stock is random and requires known materials; salvage is a conservative value estimate, not a guaranteed acquisition route. Actual consumed-input routes can be cheaper than the scarcity calibration. Raw ingredient bases exclude premiums and biome factors; a conversion receives 10% once, then the output biome applies once.", new decimal[1] { normal[x.Key] }), StringComparer.Ordinal);
			decimal Minimum(string id, decimal num8)
			{
				if (minimums == null || !minimums.TryGetValue(id, out value5))
				{
					return num8;
				}
				return Math.Max(num8, value5);
			}
		}

		public static bool IsReviewedPrefab(string id)
		{
			return Reviewed.Contains(id);
		}
	}
	public static class EpicSupplementValuation
	{
		public const string FixedRelicPrefab = "Andvaranaut";

		public static bool IsFixedRelic(string prefab)
		{
			return string.Equals(prefab, "Andvaranaut", StringComparison.Ordinal);
		}

		public static DifficultyEstimate FixedRelicEstimate()
		{
			return new DifficultyEstimate(new decimal[1] { 300m }, 0m, "Difficulty estimate: owner-calibrated Andvaranaut relic value300, including its canonical finder enchantment. No additional gear rarity or enchant-material premium; custom owner anchors remain authoritative. This calibration does not establish availability or a purchase route.");
		}

		public static IReadOnlyList<KeyValuePair<int, string>> ActiveRarityTargets(IReadOnlyList<double>? weights, IReadOnlyDictionary<int, string> mapped)
		{
			IEnumerable<int> source;
			if (weights != null && weights.Count != 0)
			{
				source = from i in Enumerable.Range(0, weights.Count)
					where !double.IsNaN(weights[i]) && !double.IsInfinity(weights[i]) && weights[i] > 0.0
					select i;
			}
			else
			{
				IEnumerable<int> enumerable = new int[1];
				source = enumerable;
			}
			int[] source2 = source.ToArray();
			KeyValuePair<int, string>[] valid = mapped.Where<KeyValuePair<int, string>>((KeyValuePair<int, string> x) => x.Key >= 0 && x.Key < 100 && !string.IsNullOrEmpty(x.Value)).ToArray();
			if (valid.Length == 0)
			{
				return Array.Empty<KeyValuePair<int, string>>();
			}
			return source2.Select((int i) => (from x in valid
				orderby Math.Abs(x.Key - i), x.Key
				select x).First()).Distinct().ToArray();
		}

		public static IReadOnlyList<Material> ParseAccessoryRecipe(string text)
		{
			if (string.IsNullOrWhiteSpace(text) || text.Length > 65536)
			{
				throw new ArgumentException("Missing/oversized configured accessory recipe.");
			}
			List<Material> list = new List<Material>();
			string[] array = text.Split('|');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(',');
				if (array2.Length < 2 || array2.Length > 3 || !int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0 || result > 10000 || string.IsNullOrWhiteSpace(array2[0]))
				{
					throw new ArgumentException("Invalid configured accessory ingredient.");
				}
				if (array2.Length == 3 && (!int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 < 0 || result2 > 10000))
				{
					throw new ArgumentException("Invalid configured accessory upgrade amount.");
				}
				if (result > 0)
				{
					list.Add(new Material(array2[0].Trim(), result));
				}
			}
			if (list.Count == 0 || list.Count > 100)
			{
				throw new ArgumentException("Accessory recipe has no consumed ingredients or is oversized.");
			}
			return list;
		}

		public static decimal? RawCost(IEnumerable<Material> inputs, Func<string, decimal?> price)
		{
			decimal num = default(decimal);
			decimal? num3;
			foreach (Material input in inputs)
			{
				decimal? num2 = price(input.Prefab);
				if (input.Amount > 0 && num2.HasValue)
				{
					num3 = num2;
					if (!((num3.GetValueOrDefault() <= default(decimal)) & num3.HasValue))
					{
						num3 = num2;
						decimal num4 = 1000000000000m / (decimal)input.Amount;
						if (!((num3.GetValueOrDefault() > num4) & num3.HasValue))
						{
							num += num2.Value * (decimal)input.Amount;
							if (!(num > 1000000000000m))
							{
								continue;
							}
							num3 = null;
							goto IL_011e;
						}
					}
				}
				num3 = null;
				num3 = num3;
				goto IL_011e;
			}
			if (!(num > 0m))
			{
				return null;
			}
			return num;
			IL_011e:
			return num3;
		}

		public static DifficultyEstimate Chisel(decimal legendaryShardBasis, decimal legendaryWeight, decimal weight)
		{
			if (legendaryShardBasis <= 0m || legendaryWeight <= 0m || weight <= 0m)
			{
				throw new ArgumentOutOfRangeException();
			}
			decimal num = 100000000000m;
			decimal num2 = Math.Min(num, legendaryShardBasis);
			decimal num3 = num / num2;
			if (legendaryWeight > weight)
			{
				num2 = ((legendaryWeight / num3 >= weight) ? num : (num2 * (legendaryWeight / weight)));
			}
			return new DifficultyEstimate(new decimal[1] { num2 }, 0m, "Difficulty estimate: Epic Loot Brokkr's Gift scarcity, calibrated to one cheapest live Legendary shard raw value and the active gift rarity-weight ratio. This is a socket-family balance choice, not a guaranteed recipe, measured farming time, or a claim that table weights are absolute drop probabilities.");
		}
	}
	public sealed class EpicTokenBundle
	{
		public string Source { get; }

		public ValuationBiome? Biome { get; }

		public int BudgetCoins { get; }

		public int Coins { get; }

		public IReadOnlyDictionary<string, int> Tokens { get; }

		public EpicTokenBundle(string source, ValuationBiome? biome, int budgetCoins, int coins, int forestTokens = 0, int ironTokens = 0, int goldTokens = 0)
		{
			if (string.IsNullOrWhiteSpace(source) || (biome.HasValue && !Enum.IsDefined(typeof(ValuationBiome), biome.Value)))
			{
				throw new ArgumentException("Token source identity is invalid.");
			}
			if (budgetCoins < 0 || budgetCoins > 1000000000 || coins < 0 || coins > 1000000000 || new int[3] { forestTokens, ironTokens, goldTokens }.Any((int x) => x < 0 || x > 1000000))
			{
				throw new ArgumentOutOfRangeException();
			}
			Source = source;
			Biome = biome;
			BudgetCoins = budgetCoins;
			Coins = coins;
			Tokens = new Dictionary<string, int>(StringComparer.Ordinal)
			{
				{ "ForestToken", forestTokens },
				{ "IronBountyToken", ironTokens },
				{ "GoldBountyToken", goldTokens }
			};
		}
	}
	public sealed class EpicTokenPrice
	{
		public DifficultyEstimate Estimate { get; }

		public BiomeAdjustment Biome { get; }

		public bool HasObservedBiome { get; }

		public EpicTokenPrice(DifficultyEstimate estimate, BiomeAdjustment biome, bool hasObservedBiome = false)
		{
			Estimate = estimate;
			Biome = biome;
			HasObservedBiome = hasObservedBiome;
		}
	}
	public static class EpicTokenValuation
	{
		public const string Forest = "ForestToken";

		public const string Iron = "IronBountyToken";

		public const string Gold = "GoldBountyToken";

		public static bool IsReviewedPrefab(string id)
		{
			if (!(id == "ForestToken") && !(id == "IronBountyToken"))
			{
				return id == "GoldBountyToken";
			}
			return true;
		}

		private static string Number(decimal value)
		{
			return value.ToString("0.########", CultureInfo.InvariantCulture);
		}

		public static IReadOnlyDictionary<string, EpicTokenPrice> Calculate(IEnumerable<string> registeredIds, IEnumerable<EpicTokenBundle> sourceBundles, decimal magicReferenceCoins, int ironGambleCost, int goldGambleCost, IReadOnlyDictionary<string, decimal>? anchors = null)
		{
			if (magicReferenceCoins < 1m || magicReferenceCoins > 1000000000m)
			{
				throw new ArgumentOutOfRangeException("magicReferenceCoins");
			}
			HashSet<string> hashSet = new HashSet<string>(registeredIds.Where(IsReviewedPrefab), StringComparer.Ordinal);
			EpicTokenBundle[] array = sourceBundles.Take(8193).ToArray();
			if (array.Length > 8192 || array.Any((EpicTokenBundle x) => x == null))
			{
				throw new ArgumentException("Oversized or invalid token source list.");
			}
			bool flag = ironGambleCost < 1 || goldGambleCost < 1;
			if (flag)
			{
				ironGambleCost = 5;
				goldGambleCost = 3;
			}
			Dictionary<string, decimal> weights = new Dictionary<string, decimal>(StringComparer.Ordinal)
			{
				{ "ForestToken", goldGambleCost },
				{ "IronBountyToken", goldGambleCost },
				{ "GoldBountyToken", ironGambleCost }
			};
			Dictionary<string, decimal> dictionary = new Dictionary<string, decimal>(StringComparer.Ordinal);
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal);
			Dictionary<string, BiomeAdjustment> dictionary3 = new Dictionary<string, BiomeAdjustment>(StringComparer.Ordinal);
			EpicTokenBundle[] array2 = array;
			foreach (EpicTokenBundle bundle in array2)
			{
				foreach (string item in hashSet.Where((string x) => bundle.Tokens[x] > 0))
				{
					if (bundle.Biome.HasValue && (!dictionary3.TryGetValue(item, out var value) || bundle.Biome.Value < value.Biome))
					{
						dictionary3[item] = new BiomeAdjustment(bundle.Biome.Value, "Earliest active Epic Loot token source biome: " + bundle.Source + ".");
					}
				}
			}
			array2 = array;
			foreach (EpicTokenBundle bundle2 in array2)
			{
				if (bundle2.BudgetCoins == 0)
				{
					continue;
				}
				decimal num2 = 300m * (decimal)bundle2.BudgetCoins / magicReferenceCoins;
				decimal num3 = (decimal)bundle2.Coins * 0.3m;
				foreach (KeyValuePair<string, int> item2 in bundle2.Tokens.Where<KeyValuePair<string, int>>((KeyValuePair<string, int> x) => x.Value > 0))
				{
					if (anchors != null && anchors.TryGetValue(item2.Key, out var value2))
					{
						if (value2 <= 0m || value2 > 1000000000000m)
						{
							throw new ArgumentOutOfRangeException("anchors");
						}
						num3 += value2 * (decimal)item2.Value;
					}
				}
				decimal num4 = bundle2.Tokens.Where<KeyValuePair<string, int>>(delegate(KeyValuePair<string, int> x)
				{
					if (x.Value > 0)
					{
						IReadOnlyDictionary<string, decimal>? readOnlyDictionary = anchors;
						if (readOnlyDictionary == null)
						{
							return true;
						}
						return !readOnlyDictionary.ContainsKey(x.Key);
					}
					return false;
				}).Sum((KeyValuePair<string, int> x) => weights[x.Key] * (decimal)x.Value);
				if (num3 >= num2 || num4 <= 0m)
				{
					continue;
				}
				foreach (string item3 in hashSet.Where(delegate(string x)
				{
					if (bundle2.Tokens[x] > 0)
					{
						IReadOnlyDictionary<string, decimal>? readOnlyDictionary = anchors;
						if (readOnlyDictionary == null)
						{
							return true;
						}
						return !readOnlyDictionary.ContainsKey(x);
					}
					return false;
				}))
				{
					if (!dictionary3.TryGetValue(item3, out var value3) || bundle2.Biome == value3.Biome)
					{
						decimal num5 = (num2 - num3) * weights[item3] / num4;
						if (!(num5 <= 0m) && !(num5 > 100000000000m) && (!dictionary.TryGetValue(item3, out var value4) || !(value4 <= num5)))
						{
							dictionary[item3] = num5;
							dictionary2[item3] = bundle2.Source + "; calibrated whole token/coin budget " + Number(num2) + ", reserved coin/custom-token contribution " + Number(num3) + ", weighted token units " + Number(num4) + ", this token weight " + Number(weights[item3]) + ". Source calibration uses Magic raw" + Number(300m) + " / active reference CoinsCost " + Number(magicReferenceCoins) + "; Gold/Iron relative purchasing power follows active gamble token counts " + ironGambleCost + "/" + goldGambleCost + ". ";
						}
					}
				}
			}
			Dictionary<string, EpicTokenPrice> dictionary4 = new Dictionary<string, EpicTokenPrice>(StringComparer.Ordinal);
			foreach (string item4 in hashSet)
			{
				decimal value6;
				string text;
				if (anchors != null && anchors.TryGetValue(item4, out var value5))
				{
					if (value5 <= 0m || value5 > 1000000000000m)
					{
						throw new ArgumentOutOfRangeException("anchors");
					}
					value6 = value5;
					text = "Explicit owner token anchor; active source biome retained. ";
				}
				else if (dictionary.TryGetValue(item4, out value6))
				{
					text = dictionary2[item4];
				}
				else
				{
					value6 = 300m * ((item4 == "ForestToken") ? 4m : ((item4 == "IronBountyToken") ? 3m : 5m));
					text = "No usable active token/coin budget was observed. Provisional reviewed scarcity calibration: Forest" + Number(1200m) + ", Iron" + Number(900m) + ", Gold" + Number(1500m) + " raw; obtainability is not established by this fallback. ";
				}
				if (flag)
				{
					text += "Invalid/disabled gamble token counts use provisional default relative weight 5/3. ";
				}
				text += "Difficulty estimate for Epic Loot currency; one shared configured coin/token budget, no duplicated co-product value. Random treasure/equipment drops are outside this token/coin calibration. Not a guaranteed exchange route, official price or measured farming time; no crafting premium. ";
				dictionary4[item4] = new EpicTokenPrice(new DifficultyEstimate(new decimal[1] { value6 }, 0m, "Difficulty estimate: Epic Loot token. " + text), dictionary3.TryGetValue(item4, out var value7) ? value7 : BiomeValuation.Unknown, dictionary3.ContainsKey(item4));
			}
			return dictionary4;
		}
	}
	public static class EpicUnidentifiedValuation
	{
		private static readonly string[] KnownBiomeNames = Enum.GetNames(typeof(ValuationBiome));

		public static bool TryKnownBiomeName(string? name, out ValuationBiome biome)
		{
			biome = ValuationBiome.Meadows;
			if (name != null && KnownBiomeNames.Contains<string>(name, StringComparer.Ordinal))
			{
				return Enum.TryParse<ValuationBiome>(name, out biome);
			}
			return false;
		}

		public static DifficultyEstimate Estimate(ValuationBiome biome, int? rarity, decimal materialBaseValue, int maxQuality, string provenance)
		{
			if (rarity.HasValue && (rarity.Value < 0 || rarity.Value > 99))
			{
				throw new ArgumentOutOfRangeException("rarity");
			}
			if (materialBaseValue <= 0m || materialBaseValue > 1000000000000m || maxQuality < 1 || maxQuality > 100)
			{
				throw new ArgumentOutOfRangeException();
			}
			if (string.IsNullOrWhiteSpace(provenance))
			{
				throw new ArgumentException("C