Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Fate Forge v0.4.8
BepInEx/plugins/FateForge/FateForge.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
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
The result has been truncated due to the large size, download it to view full contents!
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