Decompiled source of Inscribe v0.2.8

plugins/Inscribe/Inscribe.Core.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using ExtraPotions.Inscribe.Config;
using ExtraPotions.Inscribe.Rarity;
using ExtraPotions.Inscribe.Runes;
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("ExtraPotions")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")]
[assembly: AssemblyFileVersion("0.2.8.0")]
[assembly: AssemblyInformationalVersion("0.2.8+source.31CDC68E1D064A47")]
[assembly: AssemblyProduct("Inscribe.Core")]
[assembly: AssemblyTitle("Inscribe.Core")]
[assembly: AssemblyVersion("0.2.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 ExtraPotions.Inscribe
{
	public static class InscribeCore
	{
		public const string PluginGuid = "com.meadworks.inscribe";

		public const string ProductName = "Inscribe";

		public const string ProductVersion = "0.2.8";

		public const string Family = "Fang";

		public const string TokenNamespace = "inscribe";

		public const string TokenPrefix = "meadworks_inscribe_";

		public const string SupportedGameVersion = "1.0.17";
	}
	public interface IDice
	{
		float Value();
	}
	public sealed class FixedDice : IDice
	{
		private readonly float[] values;

		private int next;

		public FixedDice(params float[] values)
		{
			this.values = ((values == null || values.Length == 0) ? new float[1] : values);
		}

		public float Value()
		{
			float result = values[(next < values.Length) ? next : (values.Length - 1)];
			next++;
			return result;
		}
	}
}
namespace ExtraPotions.Inscribe.Runes
{
	public sealed class BiomeEssence
	{
		public string Id { get; }

		public string Biome { get; }

		public string IconItem { get; }

		public (float R, float G, float B) Color { get; }

		public IReadOnlyList<string> Inscriptions { get; }

		public string TextKey => "essence_" + Id;

		public BiomeEssence(string id, string biome, string iconItem, float r, float g, float b, params string[] inscriptions)
		{
			Id = id;
			Biome = biome;
			IconItem = iconItem;
			Color = (R: r, G: g, B: b);
			Inscriptions = inscriptions;
		}
	}
	public static class BiomeEssences
	{
		public const int OriginalCount = 5;

		public static readonly string[] OriginalIds = new string[5] { "beast", "stone", "elemental", "spirit", "grave" };

		public static readonly BiomeEssence[] All = new BiomeEssence[3]
		{
			new BiomeEssence("mist", "Mistlands", "Carapace", 0.56f, 0.78f, 0.86f, "mistbane", "huginns_eye", "windstep", "ravens_glide", "spring_heeled", "light_leap", "soft_landing", "mountain_goat", "marshstrider", "fleetfoot", "fleet", "stride", "momentum", "evaders_fury", "nimble", "elusive", "ghostwalk", "shadowmeld", "soft_tread"),
			new BiomeEssence("ash", "Ashlands", "CharredBone", 1f, 0.55f, 0.32f, "battle_rush", "blood_up", "lone_blade", "steel_rhythm", "godslayer", "unshaken", "skirmisher", "easy_draw", "thrifty_quiver", "throwing_grip", "returning", "bifrost_step"),
			new BiomeEssence("frost", "DeepNorth", "FrostCore", 0.82f, 0.93f, 1f, "hearthbound", "hearthlight", "hearthglow", "oilskin", "sealegs", "hearty_appetite", "well_fed", "deep_rest", "gourmand", "long_brew", "potent_brew", "bottomless_flask", "brewers_haste", "swift_draught", "steady_breath", "hardy", "hale", "warding", "purifying", "strong_swimmer")
		};

		public static int Offered(bool biomeEssences)
		{
			return 5 + (biomeEssences ? All.Length : 0);
		}

		public static bool IsOffered(int index, bool biomeEssences)
		{
			if (index >= 0)
			{
				return index < Offered(biomeEssences);
			}
			return false;
		}
	}
	public static class BossPhases
	{
		private static readonly HashSet<string> Earlier = new HashSet<string>(StringComparer.Ordinal) { "FrozenKing", "FrozenKing_p2" };

		public static IReadOnlyCollection<string> EarlierPhases => Earlier;

		public static bool IsEarlierPhase(string? prefab)
		{
			if (prefab != null)
			{
				return Earlier.Contains(prefab);
			}
			return false;
		}
	}
	public enum RecastFocus
	{
		Unrestricted,
		Offense,
		Defense,
		Utility
	}
	public enum RecastSide
	{
		Prefix,
		Suffix
	}
	public enum RuneVerb
	{
		Promote,
		Add,
		Strip,
		Corrupt,
		Reroll,
		Hone
	}
	public enum SerpentOutcome
	{
		SealOnly,
		AddInscription,
		ChaoticReroll
	}
	public readonly struct OutcomeWeight
	{
		public SerpentOutcome Outcome { get; }

		public float Weight { get; }

		public OutcomeWeight(SerpentOutcome outcome, float weight)
		{
			Outcome = outcome;
			Weight = weight;
		}
	}
	public sealed class RuneDef
	{
		public string Id { get; }

		public string Prefab { get; }

		public RuneVerb Verb { get; }

		public IReadOnlyList<string> AppliesTo { get; }

		public bool Confirm { get; }

		public int Overflow { get; }

		public IReadOnlyList<OutcomeWeight> Outcomes { get; }

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

		public bool Enabled { get; }

		public int Stack { get; }

		public float ItemWeight { get; }

		public RuneDef(string id, RuneVerb verb, IReadOnlyList<string> appliesTo, bool confirm = false, int overflow = 1, IReadOnlyList<OutcomeWeight>? outcomes = null, IReadOnlyDictionary<string, int>? cost = null, bool enabled = true, int stack = 50, float itemWeight = 0.2f)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				throw new ArgumentException("Expected a rune id.", "id");
			}
			Id = id;
			Prefab = PrefabOf(id);
			Verb = verb;
			AppliesTo = appliesTo?.ToArray() ?? Array.Empty<string>();
			Confirm = confirm;
			Overflow = Math.Max(0, overflow);
			Outcomes = outcomes?.ToArray() ?? Array.Empty<OutcomeWeight>();
			Cost = cost ?? new Dictionary<string, int>();
			Enabled = enabled;
			Stack = stack;
			ItemWeight = itemWeight;
		}

		public static string PrefabOf(string id)
		{
			return string.Concat("ECF_", string.Concat(from part in id.Split(new char[1] { '_' }, StringSplitOptions.RemoveEmptyEntries)
				select char.ToUpperInvariant(part[0]) + part.Substring(1)));
		}

		public bool AppliesToRarity(string rarityId)
		{
			return AppliesTo.Contains(rarityId);
		}

		public int CostFor(string rarityId)
		{
			if (!Cost.TryGetValue(rarityId, out var value))
			{
				return 1;
			}
			return Math.Max(value, 0);
		}

		public RuneDef WithEnabled(bool enabled)
		{
			return new RuneDef(Id, Verb, AppliesTo, Confirm, Overflow, Outcomes, Cost, enabled, Stack, ItemWeight);
		}

		public override string ToString()
		{
			return Id;
		}
	}
	public sealed class RuneCatalog
	{
		private readonly RuneDef[] runes;

		public static RuneCatalog Default { get; } = new RuneCatalog(new RuneDef[5]
		{
			new RuneDef("ascension", RuneVerb.Promote, new string[3] { "normal", "magic", "rare" }),
			new RuneDef("recasting", RuneVerb.Reroll, new string[1] { "magic" }),
			new RuneDef("honing", RuneVerb.Hone, new string[4] { "magic", "rare", "mw_epic", "mw_legendary" }),
			new RuneDef("cleansing", RuneVerb.Strip, new string[4] { "magic", "rare", "mw_epic", "mw_legendary" }, confirm: true),
			new RuneDef("serpent", RuneVerb.Corrupt, new string[4] { "magic", "rare", "mw_epic", "mw_legendary" }, confirm: true, 1, new OutcomeWeight[3]
			{
				new OutcomeWeight(SerpentOutcome.SealOnly, 35f),
				new OutcomeWeight(SerpentOutcome.AddInscription, 35f),
				new OutcomeWeight(SerpentOutcome.ChaoticReroll, 30f)
			})
		});

		public IReadOnlyList<RuneDef> Runes => runes;

		public RuneCatalog(IEnumerable<RuneDef> runes)
		{
			this.runes = runes.ToArray();
			if (this.runes.Select((RuneDef r) => r.Id).Distinct<string>(StringComparer.Ordinal).Count() != this.runes.Length)
			{
				throw new ArgumentException("Duplicate rune id.", "runes");
			}
		}

		public RuneDef? Find(string? id)
		{
			if (id != null)
			{
				return runes.FirstOrDefault((RuneDef r) => r.Id == id);
			}
			return null;
		}

		public RuneDef? ByPrefab(string? prefab)
		{
			if (prefab != null)
			{
				return runes.FirstOrDefault((RuneDef r) => r.Prefab == prefab);
			}
			return null;
		}
	}
	public static class LegacyRunes
	{
		public static readonly IReadOnlyDictionary<string, string> Successors = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["awakening"] = "ascension",
			["tempering"] = "honing"
		};

		private static readonly Dictionary<string, string> PrefabSuccessors = Successors.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> p) => RuneDef.PrefabOf(p.Key), (KeyValuePair<string, string> p) => RuneDef.PrefabOf(p.Value), StringComparer.Ordinal);

		public static IEnumerable<string> Ids => Successors.Keys;

		public static bool IsLegacy(string? id)
		{
			if (id != null)
			{
				return Successors.ContainsKey(id);
			}
			return false;
		}

		public static string? SuccessorOf(string? id)
		{
			if (id == null || !Successors.TryGetValue(id, out string value))
			{
				return null;
			}
			return value;
		}

		public static string? SuccessorPrefab(string? prefab)
		{
			if (prefab == null || !PrefabSuccessors.TryGetValue(prefab, out string value))
			{
				return null;
			}
			return value;
		}

		public static int MoveCounts(IDictionary<string, int> counts)
		{
			if (counts == null)
			{
				throw new ArgumentNullException("counts");
			}
			int num = 0;
			foreach (KeyValuePair<string, string> successor in Successors)
			{
				if (counts.TryGetValue(successor.Key, out var value) && value > 0)
				{
					counts.TryGetValue(successor.Value, out var value2);
					counts[successor.Value] = value2 + value;
					counts[successor.Key] = 0;
					num += value;
				}
			}
			return num;
		}
	}
	public static class Weighted
	{
		public static int Pick(IReadOnlyList<float> weights, IDice dice)
		{
			double num = 0.0;
			for (int i = 0; i < weights.Count; i++)
			{
				num += (double)Math.Max(weights[i], 0f);
			}
			if (num <= 0.0)
			{
				return -1;
			}
			double num2 = (double)dice.Value() * num;
			int result = -1;
			for (int j = 0; j < weights.Count; j++)
			{
				float num3 = Math.Max(weights[j], 0f);
				if (!(num3 <= 0f))
				{
					result = j;
					if (num2 < (double)num3)
					{
						return j;
					}
					num2 -= (double)num3;
				}
			}
			return result;
		}
	}
	public interface IInscriptionSource
	{
		IReadOnlyList<Inscription>? Add(ItemState state, RarityDef rarity, int count, int overflow);

		IReadOnlyList<Inscription>? Fresh(ItemState state, RarityDef rarity);

		IReadOnlyList<Inscription>? Chaotic(ItemState state, RarityDef rarity);
	}
	public sealed class RuneJob
	{
		public RuneDef? Rune { get; }

		public ItemState State { get; set; }

		public bool TargetInOwnInventory { get; set; }

		public bool RuneWithinReach { get; set; }

		public bool IsMagicBase { get; set; }

		public bool IsEquipped { get; set; }

		public bool ModifyEquippedItems { get; set; } = true;

		public int RuneStack { get; set; }

		public RuneJob(RuneDef? rune, ItemState state)
		{
			Rune = rune;
			State = state ?? throw new ArgumentNullException("state");
		}
	}
	public sealed class RuneResult
	{
		public string? Refusal { get; }

		public string? Feedback { get; }

		public ItemState? State { get; }

		public int Cost { get; }

		public IReadOnlyList<string> Words { get; }

		public bool Refused => Refusal != null;

		private RuneResult(string? refusal, string? feedback, ItemState? state, int cost, string[] words)
		{
			Refusal = refusal;
			Feedback = feedback;
			State = state;
			Cost = cost;
			Words = words;
		}

		public static RuneResult Refuse(string id, params string[] words)
		{
			return new RuneResult(id, null, null, 0, words);
		}

		public static RuneResult Success(ItemState state, int cost, string feedback, params string[] words)
		{
			return new RuneResult(null, feedback, state, cost, words);
		}
	}
	public static class RunePipeline
	{
		public static RuneResult Evaluate(RuneJob job, RarityLadder ladder, IInscriptionSource source, IDice dice)
		{
			if (job == null)
			{
				throw new ArgumentNullException("job");
			}
			if (ladder == null)
			{
				throw new ArgumentNullException("ladder");
			}
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (dice == null)
			{
				throw new ArgumentNullException("dice");
			}
			if (!job.TargetInOwnInventory)
			{
				return RuneResult.Refuse("not_own_inventory");
			}
			if (!job.RuneWithinReach)
			{
				return RuneResult.Refuse("stone_out_of_reach");
			}
			if (!job.IsMagicBase)
			{
				return RuneResult.Refuse("not_magic_base");
			}
			if (job.State.IsNewerFormat)
			{
				return RuneResult.Refuse("newer_format");
			}
			RuneDef rune = job.Rune;
			if (rune == null || !rune.Enabled)
			{
				return RuneResult.Refuse("stone_disabled", rune?.Id ?? "");
			}
			if (job.State.IsSealed)
			{
				return RuneResult.Refuse("sealed");
			}
			if (job.IsEquipped && !job.ModifyEquippedItems)
			{
				return RuneResult.Refuse("equipped");
			}
			RarityDef rarityDef = ladder.Of(job.State);
			if (rarityDef == null)
			{
				return RuneResult.Refuse("unknown_rarity");
			}
			if (rune.Verb == RuneVerb.Promote && AtTop(ladder, rarityDef))
			{
				return RuneResult.Refuse("ascension_top", rarityDef.Id);
			}
			if (!rune.AppliesToRarity(rarityDef.Id))
			{
				return RuneResult.Refuse("wrong_rarity", rune.Id, rarityDef.Id);
			}
			int num = rune.CostFor(rarityDef.Id);
			if (job.RuneStack < num)
			{
				return RuneResult.Refuse("not_enough_stones", num.ToString(CultureInfo.InvariantCulture), rune.Id);
			}
			return Verb(job, rune, rarityDef, num, ladder, source, dice);
		}

		private static RuneResult Verb(RuneJob job, RuneDef rune, RarityDef rarity, int cost, RarityLadder ladder, IInscriptionSource source, IDice dice)
		{
			ItemState state = job.State;
			switch (rune.Verb)
			{
			case RuneVerb.Promote:
			{
				RarityDef rarityDef = ladder.Next(rarity);
				if (rarityDef == null || rarityDef.SourceOnly)
				{
					return RuneResult.Refuse("ascension_top", rarity.Id);
				}
				ItemState itemState = state.WithRarity(rarityDef.Id);
				int num = ladder.PromotionAdds(state.Count, rarityDef);
				IReadOnlyList<Inscription> readOnlyList3;
				if (num != 0)
				{
					readOnlyList3 = source.Add(itemState, rarityDef, num, 0);
				}
				else
				{
					IReadOnlyList<Inscription> readOnlyList4 = Array.Empty<Inscription>();
					readOnlyList3 = readOnlyList4;
				}
				IReadOnlyList<Inscription> readOnlyList5 = readOnlyList3;
				if (readOnlyList5 != null)
				{
					return RuneResult.Success(itemState.Adding(readOnlyList5), cost, "promoted", rarityDef.Id);
				}
				return RuneResult.Refuse("no_eligible_affix");
			}
			case RuneVerb.Add:
			{
				if (state.Count >= rarity.MaxInscriptions)
				{
					return RuneResult.Refuse("affixes_full");
				}
				IReadOnlyList<Inscription> readOnlyList2 = source.Add(state, rarity, 1, 0);
				if (readOnlyList2 != null)
				{
					return RuneResult.Success(state.Adding(readOnlyList2), cost, "affix_added", readOnlyList2[0].Id);
				}
				return RuneResult.Refuse("no_eligible_affix");
			}
			case RuneVerb.Reroll:
			{
				if (rarity.IsBase)
				{
					return RuneResult.Refuse("wrong_rarity", rune.Id, rarity.Id);
				}
				IReadOnlyList<Inscription> readOnlyList = source.Fresh(state, rarity);
				if (readOnlyList != null)
				{
					return RuneResult.Success(state.WithInscriptions(readOnlyList), cost, "rerolled");
				}
				return RuneResult.Refuse("no_eligible_affix");
			}
			case RuneVerb.Strip:
				return RuneResult.Success(new ItemState(null, null, null, state.IsNewerFormat), cost, "stripped");
			case RuneVerb.Corrupt:
				return Corrupt(state, rune, rarity, cost, source, dice);
			default:
				return RuneResult.Refuse("stone_disabled", rune.Id);
			}
		}

		public static bool AtTop(RarityLadder ladder, RarityDef rarity)
		{
			if (rarity.IsBase)
			{
				return false;
			}
			return ladder.Next(rarity)?.SourceOnly ?? true;
		}

		private static RuneResult Corrupt(ItemState state, RuneDef rune, RarityDef rarity, int cost, IInscriptionSource source, IDice dice)
		{
			int num = Weighted.Pick(rune.Outcomes.Select((OutcomeWeight o) => o.Weight).ToList(), dice);
			SerpentOutcome serpentOutcome = ((num >= 0) ? rune.Outcomes[num].Outcome : SerpentOutcome.SealOnly);
			if (serpentOutcome == SerpentOutcome.AddInscription && !rarity.IsBase && state.Count < rarity.MaxInscriptions + rune.Overflow)
			{
				IReadOnlyList<Inscription> readOnlyList = source.Add(state, rarity, 1, rune.Overflow);
				if (readOnlyList != null)
				{
					return RuneResult.Success(state.Adding(readOnlyList).Sealed("serpent"), cost, "corrupt_add", readOnlyList[0].Id);
				}
			}
			if (serpentOutcome == SerpentOutcome.ChaoticReroll)
			{
				IReadOnlyList<Inscription> readOnlyList2 = source.Chaotic(state, rarity);
				if (readOnlyList2 != null && readOnlyList2.Count > 0)
				{
					return RuneResult.Success(state.WithInscriptions(readOnlyList2).Sealed("serpent"), cost, "corrupt_chaos");
				}
			}
			return RuneResult.Success(state.Sealed("serpent"), cost, "corrupt_seal");
		}
	}
	public enum ConfirmDecision
	{
		Commit,
		AskDialog,
		RefuseConfirmRequired
	}
	public static class RuneConfirm
	{
		public static ConfirmDecision Decide(bool runeConfirms, ConfirmMode mode, bool shiftHeld, bool padInput, bool dialogAvailable)
		{
			if (!runeConfirms || mode == ConfirmMode.Off)
			{
				return ConfirmDecision.Commit;
			}
			bool flag = mode == ConfirmMode.Dialog || (mode == ConfirmMode.HoldShift && padInput && !shiftHeld);
			if (!flag && shiftHeld)
			{
				return ConfirmDecision.Commit;
			}
			if (flag && dialogAvailable)
			{
				return ConfirmDecision.AskDialog;
			}
			if (!shiftHeld)
			{
				return ConfirmDecision.RefuseConfirmRequired;
			}
			return ConfirmDecision.Commit;
		}
	}
	public enum SocketBase
	{
		None,
		Weapon,
		Staff,
		Armour
	}
	public static class SocketStones
	{
		public const string ChiselId = "dvergr_chisel";

		public const string BackpackClass = "backpack";

		public const string PickaxeClass = "pickaxe";

		public const int MaxSockets = 3;

		public const float BossGemChance = 50f;

		public const float BossChiselChance = 25f;

		private static readonly Dictionary<string, (string? Weapon, string? Staff, string? Armour)> Stats = new Dictionary<string, (string, string, string)>(StringComparer.Ordinal)
		{
			["gem_surtr"] = ("emberbrand", "primal_fury", "flameward"),
			["gem_ymir"] = ("rimebrand", "seidr_thrift", "frostward"),
			["gem_thor"] = ("stormbrand", "thors_chain", "stormward"),
			["gem_nidhogg"] = ("venombrand", "lingering_wounds", "venomward"),
			["gem_hel"] = ("spiritbrand", "soul_reaper", "spiritward"),
			["gem_tyr"] = ("honed_might", "grave_command", "resolute"),
			["gem_freyja"] = ("blood_drinker", "blood_thrift", "vigor"),
			["gem_odin"] = ("seidr_siphon", "seidr_flow", "wellspring"),
			["gem_skadi"] = ("wind_siphon", "grave_vigor", "endurance"),
			["gem_heimdall"] = ("keen_eye", "swift_casting", "mist_veil"),
			["gem_sleipnir"] = (null, null, "fleetfoot")
		};

		private static readonly float[] MagicDrop = new float[4] { 80f, 15f, 4f, 1f };

		private static readonly float[] RareDrop = new float[4] { 60f, 25f, 11f, 4f };

		public static IReadOnlyList<string> GemIds { get; } = new string[11]
		{
			"gem_surtr", "gem_ymir", "gem_thor", "gem_nidhogg", "gem_hel", "gem_tyr", "gem_freyja", "gem_odin", "gem_skadi", "gem_heimdall",
			"gem_sleipnir"
		};

		public static IReadOnlyList<string> Bosses { get; } = new string[8] { "Eikthyr", "gd_king", "Bonemass", "Dragon", "GoblinKing", "SeekerQueen", "Fader", "FrozenKing_p3" };

		public static bool IsGem(string? id)
		{
			if (id != null)
			{
				return Stats.ContainsKey(id);
			}
			return false;
		}

		public static bool IsSocketStone(string? id)
		{
			if (!(id == "dvergr_chisel"))
			{
				return IsGem(id);
			}
			return true;
		}

		public static SocketBase BaseOf(string? group, string? classId)
		{
			switch (group)
			{
			case "onehand":
			case "twohand":
			case "ranged":
				return SocketBase.Weapon;
			case "magic":
				return SocketBase.Staff;
			case "armour":
				return SocketBase.Armour;
			case "offhand":
				if (!(classId == "light"))
				{
					return SocketBase.Armour;
				}
				return SocketBase.None;
			case "jewel":
				if (!(classId == "backpack"))
				{
					return SocketBase.None;
				}
				return SocketBase.Armour;
			case "tools":
				if (!(classId == "pickaxe"))
				{
					return SocketBase.None;
				}
				return SocketBase.Weapon;
			default:
				return SocketBase.None;
			}
		}

		public static string? StatFor(string gemId, SocketBase socketBase)
		{
			if (!Stats.TryGetValue(gemId, out (string, string, string) value))
			{
				return null;
			}
			return socketBase switch
			{
				SocketBase.Armour => value.Item3, 
				SocketBase.Staff => value.Item2, 
				SocketBase.Weapon => value.Item1, 
				_ => null, 
			};
		}

		public static float[] DropWeights(string? rarityId)
		{
			return ((rarityId == "rare") ? RareDrop : MagicDrop).ToArray();
		}

		public static double CutChance(string? rarityId, int sockets)
		{
			float[] array = CutWeights(rarityId);
			if (sockets >= 1 && sockets <= 3)
			{
				return 100.0 * (double)array[sockets - 1] / (double)array.Sum();
			}
			return 0.0;
		}

		public static int Cut(string? rarityId, IDice dice)
		{
			return 1 + Math.Max(0, Weighted.Pick(CutWeights(rarityId), dice));
		}

		public static int BossGemRolls(int stars)
		{
			return 1 + Math.Max(0, stars);
		}

		private static float[] CutWeights(string? rarityId)
		{
			return DropWeights(rarityId).Skip(1).ToArray();
		}
	}
	public static class SalvageRune
	{
		public const float Chance = 0.25f;

		public static string? RuneFor(RuneCatalog runes, RarityLadder ladder, string? rarityId)
		{
			RarityDef rarity = ladder.Find(rarityId);
			if (rarity == null || rarity.Index == 0)
			{
				return null;
			}
			RarityDef below = ladder.Rarities.LastOrDefault((RarityDef r) => r.Index < rarity.Index);
			if (below == null)
			{
				return null;
			}
			return runes.Runes.FirstOrDefault((RuneDef r) => r.Enabled && r.Verb == RuneVerb.Promote && r.AppliesToRarity(below.Id))?.Id;
		}

		public static bool Rolls(IDice dice)
		{
			return dice.Value() < 0.25f;
		}
	}
	public static class TargetedRules
	{
		public static bool CanHone(bool exists, float weight, int unlockLevel, int shown, int itemLevel, int closed)
		{
			if (exists && weight > 0f && unlockLevel <= itemLevel)
			{
				return shown > closed;
			}
			return false;
		}
	}
}
namespace ExtraPotions.Inscribe.Rarity
{
	public enum InscriptionKind
	{
		Prefix,
		Suffix
	}
	public sealed class Inscription
	{
		public string Id { get; }

		public InscriptionKind Kind { get; }

		public int Grade { get; }

		public float Value { get; }

		public bool Dormant { get; }

		public Inscription(string id, InscriptionKind kind, int grade, float value, bool dormant = false)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				throw new ArgumentException("Expected an inscription id.", "id");
			}
			Id = id;
			Kind = kind;
			Grade = grade;
			Value = value;
			Dormant = dormant;
		}

		public override string ToString()
		{
			return Id;
		}
	}
	public sealed class ItemState
	{
		public static ItemState Normal { get; } = new ItemState(null, null, null);

		public string? RarityId { get; }

		public IReadOnlyList<Inscription> Inscriptions { get; }

		public string? SealedReason { get; }

		public bool IsSealed => SealedReason != null;

		public bool IsNewerFormat { get; }

		public int Count => Inscriptions.Count;

		public ItemState(string? rarityId, IReadOnlyList<Inscription>? inscriptions, string? sealedReason, bool newerFormat = false)
		{
			RarityId = (string.IsNullOrEmpty(rarityId) ? null : rarityId);
			Inscriptions = inscriptions?.ToArray() ?? Array.Empty<Inscription>();
			SealedReason = (string.IsNullOrEmpty(sealedReason) ? null : sealedReason);
			IsNewerFormat = newerFormat;
		}

		public ItemState WithRarity(string? rarityId)
		{
			return new ItemState(rarityId, Inscriptions, SealedReason, IsNewerFormat);
		}

		public ItemState WithInscriptions(IEnumerable<Inscription> inscriptions)
		{
			return new ItemState(RarityId, inscriptions.ToArray(), SealedReason, IsNewerFormat);
		}

		public ItemState Adding(IEnumerable<Inscription> added)
		{
			return WithInscriptions(Inscriptions.Concat(added));
		}

		public ItemState Sealed(string reason)
		{
			return new ItemState(RarityId, Inscriptions, reason, IsNewerFormat);
		}
	}
	public sealed class InscriptionLimits
	{
		private readonly int maxPrefixes;

		private readonly int maxSuffixes;

		private int prefixes;

		private int suffixes;

		private InscriptionLimits(int maxPrefixes, int maxSuffixes)
		{
			this.maxPrefixes = maxPrefixes;
			this.maxSuffixes = maxSuffixes;
		}

		public static InscriptionLimits For(ItemState state, RarityDef? rarity, int overflow)
		{
			overflow = Math.Max(overflow, 0);
			InscriptionLimits inscriptionLimits = ((rarity == null) ? new InscriptionLimits(1073741823, 1073741823) : new InscriptionLimits(rarity.MaxPrefixes + overflow, rarity.MaxSuffixes + overflow));
			foreach (Inscription inscription in state.Inscriptions)
			{
				inscriptionLimits.Count(inscription.Kind);
			}
			return inscriptionLimits;
		}

		public bool Allows(InscriptionKind kind)
		{
			if (kind != InscriptionKind.Prefix)
			{
				return suffixes < maxSuffixes;
			}
			return prefixes < maxPrefixes;
		}

		public void Count(InscriptionKind kind)
		{
			if (kind == InscriptionKind.Prefix)
			{
				prefixes++;
			}
			else
			{
				suffixes++;
			}
		}
	}
	public static class MagicBase
	{
		public static bool Is(int maxStack, bool classRolls, bool isRune, bool vetoed)
		{
			if (maxStack == 1 && classRolls && !isRune)
			{
				return !vetoed;
			}
			return false;
		}

		public static IReadOnlyList<string> StackableWarnings(IEnumerable<(string Prefab, int MaxStack, bool ClassRolls)> items)
		{
			return (from i in items
				where i.MaxStack > 1 && i.ClassRolls
				select i.Prefab).ToList();
		}
	}
	public sealed class RarityDef
	{
		public int TierFloor { get; }

		public bool SourceOnly { get; }

		public string Id { get; }

		public int Index { get; }

		public string Color { get; }

		public bool Glow { get; }

		public int MinInscriptions { get; }

		public int MaxInscriptions { get; }

		public int MaxPrefixes { get; }

		public int MaxSuffixes { get; }

		public float DropWeight { get; }

		public bool IsBase => Index == 0;

		public RarityDef(string id, int index, string color, bool glow, int minInscriptions, int maxInscriptions, int? maxPrefixes = null, int? maxSuffixes = null, float dropWeight = 1f, int tierFloor = 0, bool sourceOnly = false)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				throw new ArgumentException("Expected a rarity id.", "id");
			}
			if (minInscriptions < 0 || maxInscriptions < minInscriptions)
			{
				throw new ArgumentException(id + ": expected 0 <= min <= max.", "minInscriptions");
			}
			Id = id;
			Index = index;
			Color = color;
			Glow = index != 0 && glow;
			MinInscriptions = minInscriptions;
			MaxInscriptions = maxInscriptions;
			MaxPrefixes = maxPrefixes ?? maxInscriptions;
			MaxSuffixes = maxSuffixes ?? maxInscriptions;
			DropWeight = Math.Max(0f, dropWeight);
			TierFloor = Math.Max(0, tierFloor);
			SourceOnly = sourceOnly;
		}

		public int Limit(InscriptionKind kind)
		{
			if (kind != InscriptionKind.Prefix)
			{
				return MaxSuffixes;
			}
			return MaxPrefixes;
		}

		public override string ToString()
		{
			return Id;
		}
	}
	public sealed class RollingSettings
	{
		public float AllowedClosedFraction { get; }

		public int PromoteAddsAtLeast { get; }

		public IReadOnlyDictionary<string, IReadOnlyDictionary<int, float>> CountWeights { get; }

		public RollingSettings(float allowedClosedFraction = 0.334f, int promoteAddsAtLeast = 1, IReadOnlyDictionary<string, IReadOnlyDictionary<int, float>>? countWeights = null)
		{
			AllowedClosedFraction = allowedClosedFraction;
			PromoteAddsAtLeast = Math.Max(0, promoteAddsAtLeast);
			CountWeights = countWeights ?? new Dictionary<string, IReadOnlyDictionary<int, float>>();
		}
	}
	public sealed class RarityLadder
	{
		private readonly RarityDef[] rarities;

		public static RarityLadder Default { get; } = new RarityLadder(new RarityDef[5]
		{
			new RarityDef("normal", 0, "#FFFFFF", glow: false, 0, 0, null, null, 0f),
			new RarityDef("magic", 1, "#1EFF00", glow: true, 2, 2, 1, 1),
			new RarityDef("rare", 2, "#0070DD", glow: true, 3, 3, 2, 2),
			new RarityDef("mw_epic", 3, "#A335EE", glow: true, 4, 4, 2, 2, 1f, 4),
			new RarityDef("mw_legendary", 4, "#FFB400", glow: true, 5, 5, 3, 3, 0f, 2, sourceOnly: true)
		}, new RollingSettings());

		public IReadOnlyList<RarityDef> Rarities => rarities;

		public RollingSettings Rolling { get; }

		public RarityDef Base => rarities[0];

		public RarityLadder(IEnumerable<RarityDef> rarities, RollingSettings rolling)
		{
			this.rarities = (rarities ?? throw new ArgumentNullException("rarities")).OrderBy((RarityDef r) => r.Index).ToArray();
			if (this.rarities.Length == 0 || this.rarities[0].Index != 0)
			{
				throw new ArgumentException("A ladder starts with its base rarity at index 0.", "rarities");
			}
			if (this.rarities.Select((RarityDef r) => r.Id).Distinct<string>(StringComparer.Ordinal).Count() != this.rarities.Length)
			{
				throw new ArgumentException("Duplicate rarity id.", "rarities");
			}
			Rolling = rolling ?? throw new ArgumentNullException("rolling");
		}

		public RarityDef? Find(string? id)
		{
			if (id != null)
			{
				return rarities.FirstOrDefault((RarityDef r) => r.Id == id);
			}
			return null;
		}

		public RarityDef? Of(ItemState state)
		{
			if (state.RarityId != null)
			{
				return Find(state.RarityId);
			}
			return Base;
		}

		public RarityDef? Next(RarityDef rarity)
		{
			int num = Array.IndexOf(rarities, rarity);
			if (num < 0 || num + 1 >= rarities.Length)
			{
				return null;
			}
			return rarities[num + 1];
		}

		public int PromotionAdds(int count, RarityDef to)
		{
			return Math.Min(Math.Max(to.MinInscriptions - count, Rolling.PromoteAddsAtLeast), Math.Max(to.MaxInscriptions - count, 0));
		}
	}
	public static class RarityTiers
	{
		public const string Epic = "mw_epic";

		public const string Legendary = "mw_legendary";

		public static readonly string[] Ids = new string[5] { "normal", "magic", "rare", "mw_epic", "mw_legendary" };

		private static readonly float[] UncommonSockets = new float[4] { 80f, 15f, 4f, 1f };

		private static readonly float[] RareSockets = new float[4] { 60f, 25f, 11f, 4f };

		private static readonly float[] EpicSockets = new float[4] { 35f, 35f, 22f, 8f };

		private static readonly float[] LegendarySockets = new float[4] { 0f, 40f, 40f, 20f };

		public static float[] SocketWeights(string? rarityId)
		{
			return rarityId switch
			{
				"rare" => RareSockets, 
				"mw_epic" => EpicSockets, 
				"mw_legendary" => LegendarySockets, 
				_ => UncommonSockets, 
			};
		}
	}
	public static class LegendarySources
	{
		public const string Boss = "boss";

		public const string Raid = "raid";

		public const string Capability = "meadworks.inscribe.legendary_reward";

		public static readonly Version Version = new Version(1, 0);

		public static bool Allows(string? source, bool bosses, bool raids)
		{
			if (!string.Equals(source, "boss", StringComparison.Ordinal))
			{
				return string.Equals(source, "raid", StringComparison.Ordinal) && raids;
			}
			return bosses;
		}

		public static bool Rolls(float percent, double unit)
		{
			if (percent > 0f)
			{
				return unit * 100.0 < (double)percent;
			}
			return false;
		}
	}
	public static class GemRecovery
	{
		public static readonly float[] Defaults = new float[5] { 100f, 90f, 75f, 50f, 25f };

		public static int TierOf(string? rarityId)
		{
			if (rarityId == null)
			{
				return 0;
			}
			int num = Array.IndexOf<string>(RarityTiers.Ids, rarityId);
			if (num < 0)
			{
				return RarityTiers.Ids.Length - 1;
			}
			return num;
		}

		public static float PercentFor(string? rarityId, float[] percents)
		{
			return Math.Max(0f, Math.Min(100f, percents[Math.Min(TierOf(rarityId), percents.Length - 1)]));
		}

		public static bool Returns(float percent, double unit)
		{
			if (!(percent >= 100f))
			{
				if (percent > 0f)
				{
					return unit * 100.0 < (double)percent;
				}
				return false;
			}
			return true;
		}
	}
}
namespace ExtraPotions.Inscribe.Localization
{
	public readonly struct TextKey
	{
		public string Key { get; }

		public int Arguments { get; }

		public TextKey(string key, int arguments)
		{
			Key = key;
			Arguments = arguments;
		}

		public override string ToString()
		{
			return Key;
		}
	}
	public static class InscribeTextKeys
	{
		public const string OffNoCrucible = "off_no_crucible";

		public const string OffNoInscribe = "off_no_inscribe";

		public const string ImportDone = "import_done";

		public const string ImportSyncIncomplete = "import_sync_incomplete";

		public const string ImportRecoveryIncomplete = "import_recovery_incomplete";

		public const string CheatItemCommands = "cheat_item_commands";

		public const string StackableGear = "stackable_gear";

		public const string EnhancementConverted = "enhancement_converted";

		public const string ScrollsConverted = "scrolls_converted";

		public const string ConversionNoRoom = "enhancement_convert_no_room";

		public const string ConversionDropped = "enhancement_convert_dropped";

		internal static readonly TextKey[] Fixed = new TextKey[35]
		{
			new TextKey("off_no_crucible", 0),
			new TextKey("off_no_inscribe", 0),
			new TextKey("import_done", 2),
			new TextKey("import_sync_incomplete", 0),
			new TextKey("import_recovery_incomplete", 0),
			new TextKey("cheat_item_commands", 0),
			new TextKey("stackable_gear", 2),
			new TextKey("recast_focus_label", 0),
			new TextKey("recast_focus_any", 0),
			new TextKey("recast_focus_offense", 0),
			new TextKey("recast_focus_defense", 0),
			new TextKey("recast_focus_utility", 0),
			new TextKey("recast_focus_help", 0),
			new TextKey("recast_side_prefix", 0),
			new TextKey("recast_side_suffix", 0),
			new TextKey("recast_side_help", 0),
			new TextKey("step_aside_rarity", 1),
			new TextKey("step_aside_enhancement", 1),
			new TextKey("enhancement_converted", 4),
			new TextKey("scrolls_converted", 2),
			new TextKey("enhancement_convert_no_room", 0),
			new TextKey("enhancement_convert_dropped", 0),
			new TextKey("step_aside_all", 0),
			new TextKey("confirm_title", 1),
			new TextKey("confirm_body", 2),
			new TextKey("tooltip_sealed", 0),
			new TextKey("tooltip_dormant", 0),
			new TextKey("tooltip_item_level", 1),
			new TextKey("tooltip_quality", 1),
			new TextKey("tooltip_inscriptions", 1),
			new TextKey("tooltip_sockets", 2),
			new TextKey("gem_salvage_lost", 1),
			new TextKey("gem_replace_chance", 1),
			new TextKey("pick_inscription_title", 1),
			new TextKey("pick_inscription_body", 2)
		};

		internal static readonly (string Id, int Arguments)[] RuneMessages = new(string, int)[26]
		{
			("not_own_inventory", 0),
			("stone_out_of_reach", 0),
			("not_magic_base", 0),
			("newer_format", 0),
			("stone_disabled", 1),
			("sealed", 0),
			("equipped", 0),
			("unknown_rarity", 0),
			("wrong_rarity", 2),
			("not_enough_stones", 2),
			("no_eligible_affix", 0),
			("affixes_full", 0),
			("confirm_required", 1),
			("affix_at_top", 0),
			("affix_is_flag", 0),
			("gem_swap_kept", 4),
			("gem_swap_lost", 4),
			("honed", 2),
			("ascension_top", 1),
			("promoted", 2),
			("affix_added", 2),
			("rerolled", 1),
			("stripped", 1),
			("corrupt_seal", 1),
			("corrupt_add", 2),
			("corrupt_chaos", 1)
		};

		public static readonly string[] MeadworksAffixes = new string[11]
		{
			"unshaken", "steady_breath", "hardy", "elusive", "warding", "hale", "fleet", "hearthglow", "purifying", "gleaner",
			"seafarer"
		};

		public static readonly string[] SettingTexts = BuildSettingTexts();

		public static IReadOnlyList<TextKey> All { get; } = Fixed.Concat(RarityLadder.Default.Rarities.Select((RarityDef r) => new TextKey(RarityName(r.Id), 0))).Concat(RuneCatalog.Default.Runes.SelectMany((RuneDef r) => new TextKey[2]
		{
			new TextKey(RuneName(r.Id), 0),
			new TextKey(RuneDescription(r.Id), 0)
		})).Concat(LegacyRunes.Ids.SelectMany((string id) => new TextKey[2]
		{
			new TextKey(RuneName(id), 0),
			new TextKey(RuneDescription(id), 0)
		}))
			.Concat(RuneMessages.Select(((string Id, int Arguments) m) => new TextKey(RuneMessage(m.Id), m.Arguments)))
			.Concat(Enumerable.Range(1, 4).SelectMany((int t) => new TextKey[2]
			{
				new TextKey(ScrollName(t), 0),
				new TextKey(ScrollDescription(t), 0)
			}))
			.Concat(SettingTexts.Select((string k) => new TextKey(k, 0)))
			.Concat(BiomeEssences.OriginalIds.Select((string id) => new TextKey("essence_" + id, 0)))
			.Concat(BiomeEssences.All.Select((BiomeEssence e) => new TextKey(e.TextKey, 0)))
			.Concat(MeadworksAffixes.SelectMany((string a) => new TextKey[3]
			{
				new TextKey(AffixName(a), 0),
				new TextKey(AffixLine(a), 1),
				new TextKey(AffixShort(a), 1)
			}))
			.ToArray();

		public static string RarityName(string rarityId)
		{
			return "rarity_" + rarityId;
		}

		public static string RuneName(string runeId)
		{
			return "rune_" + runeId;
		}

		public static string RuneDescription(string runeId)
		{
			return "rune_" + runeId + "_desc";
		}

		public static string RuneMessage(string id)
		{
			return "rune_msg_" + id;
		}

		public static string ScrollName(int tier)
		{
			return "scroll_tier" + tier;
		}

		public static string ScrollDescription(int tier)
		{
			return "scroll_tier" + tier + "_desc";
		}

		public static string AffixName(string id)
		{
			return "affix_" + id;
		}

		public static string AffixLine(string id)
		{
			return "affix_" + id + "_line";
		}

		public static string AffixShort(string id)
		{
			return "affix_" + id + "_short";
		}

		public static string SettingSlug(string text)
		{
			return Regex.Replace(text.ToLowerInvariant(), "[^a-z0-9]+", "_").Trim('_');
		}

		private static string[] BuildSettingTexts()
		{
			List<string> list = new List<string>();
			foreach (InscribeSetting item in InscribeSettings.All)
			{
				string text = "cfg_" + SettingSlug(item.Section);
				if (!list.Contains(text + "_heading"))
				{
					list.Add(text + "_heading");
				}
				list.Add(text + "__" + SettingSlug(item.Key) + "_name");
				list.Add(text + "__" + SettingSlug(item.Key) + "_help");
			}
			return list.ToArray();
		}
	}
}
namespace ExtraPotions.Inscribe.Items
{
	public static class IconBadges
	{
		public static bool Glint(bool enabled, string? rarityId)
		{
			if (enabled)
			{
				return string.Equals(rarityId, "mw_legendary", StringComparison.Ordinal);
			}
			return false;
		}
	}
	public static class ItemKeys
	{
		public const string EcPrefix = "ecf_";

		public const string EcVersion = "ecf_v";

		public const string EcRarity = "ecf_rarity";

		public const string EcInscriptions = "ecf_inscriptions";

		public const string EcLegacyInscriptions = "ecf_affixes";

		public const string EcSealed = "ecf_sealed";

		public const string EcTier = "ecf_tier";

		public const string EcChestFilled = "ecf_filled";

		public const int EcCurrentFormat = 2;

		public const string EcSealedSerpent = "serpent";

		public const string EnhancementLevel = "ValheimEnhancement_Level";

		public const string EnhancementCrafter = "ValheimEnhancement_Crafter";

		public const string EnhancementConverted = "ecf_enhancement_converted";

		public static IReadOnlyList<string> EcRetired { get; } = new string[6] { "ecf_bound", "ecf_refine", "ecf_sigil", "ecf_sockets", "ecf_gems", "ecf_catalyst" };
	}
	public static class UpgradeData
	{
		public static Dictionary<string, string>? Capture(IReadOnlyDictionary<string, string> source)
		{
			Dictionary<string, string> dictionary = null;
			foreach (KeyValuePair<string, string> item in source)
			{
				if (item.Key.StartsWith("ecf_", StringComparison.Ordinal) || !(item.Key != "ValheimEnhancement_Level") || !(item.Key != "ValheimEnhancement_Crafter"))
				{
					if (dictionary == null)
					{
						dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
					}
					dictionary.Add(item.Key, item.Value);
				}
			}
			return dictionary;
		}
	}
}
namespace ExtraPotions.Inscribe.Interop
{
	public static class DecorationV1
	{
		public const string Capability = "meadworks.items.decoration";

		public static readonly Version Version = new Version(1, 0);

		public static Type ContractType => typeof(Tuple<Func<object, string>, Func<object, string>, Func<object, Tuple<string, string, float, string, string, string>[]>>);
	}
	public static class CapabilityIds
	{
		public const string ItemClasses = "meadworks.items.classes";

		public const string Decoration = "meadworks.items.decoration";

		public static readonly Version Version1 = new Version(1, 0);

		public static IReadOnlyList<string> All { get; } = new string[6] { "meadworks.items.classes", "meadworks.items.decoration", "meadworks.items.sockets", "meadworks.crafting.salvage_rune", "meadworks.crafting.salvage_gems", "meadworks.inscribe.legendary_reward" };
	}
	public static class ItemClassesV1
	{
		public static Type ContractType { get; } = typeof(Tuple<Func<string, string, bool>, Func<string, int, bool>, Func<string, bool>>);

		public static Tuple<Func<string, string, bool>, Func<string, int, bool>, Func<string, bool>> Create(ItemRegistry registry)
		{
			if (registry == null)
			{
				throw new ArgumentNullException("registry");
			}
			return Tuple.Create<Func<string, string, bool>, Func<string, int, bool>, Func<string, bool>>((string cls, string prefab) => Guard(() => registry.ClaimItem(cls, prefab)), (string prefab, int level) => Guard(() => registry.SetItemLevel(prefab, level)), (string json) => Guard(() => registry.SetCreatureLoot(json)));
		}

		private static bool Guard(Func<bool> call)
		{
			try
			{
				return call();
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	public sealed class ItemRegistry
	{
		public const int MaxLevel = 8;

		private readonly HashSet<string> classIds;

		private readonly Dictionary<string, string> claims = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> levels = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, LootProfile> loot = new Dictionary<string, LootProfile>(StringComparer.Ordinal);

		public static IReadOnlyList<string> DefaultClassIds { get; } = new string[34]
		{
			"unused", "throwable", "tankard", "practice", "cosmetic", "helmet", "chest", "legs", "cape", "trinket",
			"utility", "buckler", "tower", "shield", "light", "tool", "fishing", "pickaxe", "crossbow", "bow",
			"staff_elemental", "staff_blood", "knife", "spear", "atgeir", "fist", "greatsword", "sword_1h", "dual_axe", "battleaxe",
			"axe_1h", "sledge", "mace_1h", "backpack"
		};

		public IReadOnlyDictionary<string, LootProfile> Loot => loot;

		public IReadOnlyList<string> LastWarnings { get; private set; } = Array.Empty<string>();

		public IReadOnlyList<string> LastErrors { get; private set; } = Array.Empty<string>();

		public event Action? Changed;

		public ItemRegistry(IEnumerable<string>? classIds = null)
		{
			this.classIds = new HashSet<string>(classIds ?? DefaultClassIds, StringComparer.Ordinal);
		}

		public string? ClassOf(string prefab)
		{
			if (prefab == null || !claims.TryGetValue(prefab, out string value))
			{
				return null;
			}
			return value;
		}

		public int LevelOf(string prefab)
		{
			if (prefab == null || !levels.TryGetValue(prefab, out var value))
			{
				return 0;
			}
			return value;
		}

		public bool ClaimItem(string? classId, string? prefab)
		{
			if (string.IsNullOrEmpty(classId) || string.IsNullOrEmpty(prefab) || !classIds.Contains(classId))
			{
				return Refuse("ClaimItems: unknown class '" + classId + "' or no prefab");
			}
			claims[prefab] = classId;
			return Accept();
		}

		public bool SetItemLevel(string? prefab, int level)
		{
			if (string.IsNullOrEmpty(prefab) || level < 1 || level > 8)
			{
				return Refuse("SetItemLevel: '" + prefab + "' " + level + " refused (a prefab name and a level from 1 to 8)");
			}
			levels[prefab] = level;
			return Accept();
		}

		public bool SetCreatureLoot(string? json)
		{
			IReadOnlyList<string> errors;
			IReadOnlyList<string> warnings;
			LootProfile lootProfile = LootProfile.Parse(json, out errors, out warnings);
			LastWarnings = warnings;
			if (lootProfile == null)
			{
				LastErrors = errors;
				return false;
			}
			loot[lootProfile.Prefab] = lootProfile;
			return Accept();
		}

		private bool Refuse(string error)
		{
			LastErrors = new string[1] { error };
			return false;
		}

		private bool Accept()
		{
			LastErrors = Array.Empty<string>();
			this.Changed?.Invoke();
			return true;
		}
	}
	public sealed class LootBonus
	{
		public string Rune { get; }

		public float Chance { get; }

		public int Amount { get; }

		internal LootBonus(string rune, float chance, int amount)
		{
			Rune = rune;
			Chance = chance;
			Amount = amount;
		}
	}
	public sealed class LootProfile
	{
		public const int Tiers = 8;

		private static readonly string[] Keys = new string[11]
		{
			"prefab", "boss", "tier", "multiplier", "rune_multiplier", "gear_multiplier", "rune_rolls", "gear_rolls", "bonus", "scroll_rolls",
			"scroll_multiplier"
		};

		private static readonly Dictionary<string, string> Successors = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["shaping"] = "recasting",
			["consecrated"] = "ascension"
		};

		public string Prefab { get; private set; } = "";

		public bool Boss { get; private set; }

		public int Tier { get; private set; }

		public float Multiplier { get; private set; } = 1f;

		public float RuneMultiplier { get; private set; } = 1f;

		public float GearMultiplier { get; private set; } = 1f;

		public float ScrollMultiplier { get; private set; } = 1f;

		public int RuneRolls { get; private set; }

		public int GearRolls { get; private set; }

		public int ScrollRolls { get; private set; }

		public IReadOnlyList<LootBonus> Bonus { get; private set; } = Array.Empty<LootBonus>();

		private LootProfile()
		{
		}

		public static LootProfile? Parse(string? json, out IReadOnlyList<string> errors, out IReadOnlyList<string> warnings)
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			errors = list;
			warnings = list2;
			if (string.IsNullOrWhiteSpace(json))
			{
				list.Add("no definition given");
				return null;
			}
			object obj;
			try
			{
				obj = MiniJson.Parse(json);
			}
			catch (FormatException ex)
			{
				list.Add("the definition does not parse: " + ex.Message);
				return null;
			}
			if (!(obj is Dictionary<string, object> dictionary))
			{
				list.Add("the definition should be a JSON object");
				return null;
			}
			foreach (string item in dictionary.Keys.Where((string k) => Array.IndexOf(Keys, k) < 0))
			{
				list2.Add(item + ": unknown key, ignored");
			}
			LootProfile lootProfile = new LootProfile();
			if (!(Get(dictionary, "prefab") is string { Length: not 0 } text))
			{
				list.Add("prefab: is required: the creature's prefab name");
				return null;
			}
			lootProfile.Prefab = text;
			lootProfile.Boss = Bool(dictionary, "boss", fallback: false, list);
			lootProfile.Tier = Int(dictionary, "tier", lootProfile.Boss ? 1 : 0, 1, 8, list);
			lootProfile.Multiplier = Float(dictionary, "multiplier", 1f, 0f, float.MaxValue, list);
			lootProfile.RuneMultiplier = Float(dictionary, "rune_multiplier", 1f, 0f, float.MaxValue, list);
			lootProfile.GearMultiplier = Float(dictionary, "gear_multiplier", 1f, 0f, float.MaxValue, list);
			lootProfile.ScrollMultiplier = Float(dictionary, "scroll_multiplier", 1f, 0f, float.MaxValue, list);
			int runeRolls = Int(dictionary, "rune_rolls", 0, 0, int.MaxValue, list);
			int gearRolls = Int(dictionary, "gear_rolls", 0, 0, int.MaxValue, list);
			lootProfile.ScrollRolls = Int(dictionary, "scroll_rolls", 0, 0, int.MaxValue, list);
			if (lootProfile.Boss)
			{
				lootProfile.RuneRolls = runeRolls;
				lootProfile.GearRolls = gearRolls;
			}
			else if (dictionary.ContainsKey("rune_rolls") || dictionary.ContainsKey("gear_rolls"))
			{
				list2.Add("rune_rolls: rune_rolls and gear_rolls are for bosses (\"boss\": true); ignored");
			}
			lootProfile.Bonus = Bonuses(dictionary, list, list2);
			if (list.Count != 0)
			{
				return null;
			}
			return lootProfile;
		}

		private static IReadOnlyList<LootBonus> Bonuses(Dictionary<string, object?> map, List<string> e, List<string> w)
		{
			object obj = Get(map, "bonus");
			if (obj == null)
			{
				return Array.Empty<LootBonus>();
			}
			if (!(obj is List<object> list))
			{
				e.Add("bonus: should be a list of { rune, chance, amount }");
				return Array.Empty<LootBonus>();
			}
			List<LootBonus> list2 = new List<LootBonus>();
			foreach (object item in list)
			{
				if (!(item is Dictionary<string, object> dictionary))
				{
					e.Add("bonus: a bonus row looks like { rune: ascension, chance: 100, amount: 1 }");
					continue;
				}
				foreach (string item2 in dictionary.Keys.Where((string k) => k != "rune" && k != "chance" && k != "amount"))
				{
					w.Add("bonus." + item2 + ": unknown key, ignored");
				}
				string text = Get(dictionary, "rune") as string;
				if (text != null && Successors.TryGetValue(text, out string value))
				{
					w.Add("bonus: '" + text + "' was removed in EliteCrafting " + ((text == "shaping") ? "0.8.0" : "0.9.0") + "; paying '" + value + "' instead");
					text = value;
				}
				else
				{
					string text2 = LegacyRunes.SuccessorOf(text);
					if (text2 != null)
					{
						w.Add("bonus: '" + text + "' was folded into '" + text2 + "'; paying '" + text2 + "' instead");
						text = text2;
					}
				}
				if (text == null || (RuneCatalog.Default.Find(text) == null && !SocketStones.IsSocketStone(text)))
				{
					e.Add("bonus: '" + text + "' is not a rune, chisel or gem id (" + string.Join(", ", RuneCatalog.Default.Runes.Select((RuneDef r) => r.Id)) + ", dvergr_chisel, gem_*)");
				}
				else
				{
					list2.Add(new LootBonus(text, Float(dictionary, "chance", 100f, 0f, 100f, e), Int(dictionary, "amount", 1, 1, int.MaxValue, e)));
				}
			}
			return list2;
		}

		private static object? Get(Dictionary<string, object?> map, string key)
		{
			if (!map.TryGetValue(key, out object value))
			{
				return null;
			}
			return value;
		}

		private static bool Bool(Dictionary<string, object?> map, string key, bool fallback, List<string> e)
		{
			if (!map.ContainsKey(key))
			{
				return fallback;
			}
			object obj = map[key];
			if (obj is bool)
			{
				return (bool)obj;
			}
			e.Add(key + ": should be true or false");
			return fallback;
		}

		private static int Int(Dictionary<string, object?> map, string key, int fallback, int min, int max, List<string> e)
		{
			if (!map.ContainsKey(key))
			{
				return fallback;
			}
			if (map[key] is double num && num == Math.Floor(num) && num >= (double)min && num <= (double)max)
			{
				return (int)num;
			}
			e.Add(key + ": should be a whole number from " + min + ((max == int.MaxValue) ? " up" : (" to " + max)));
			return fallback;
		}

		private static float Float(Dictionary<string, object?> map, string key, float fallback, float min, float max, List<string> e)
		{
			if (!map.ContainsKey(key))
			{
				return fallback;
			}
			if (map[key] is double num && num >= (double)min && num <= (double)max)
			{
				return (float)num;
			}
			e.Add(key + ": should be a number from " + min + ((max == float.MaxValue) ? " up" : (" to " + max)));
			return fallback;
		}
	}
	public static class MiniJson
	{
		public static object? Parse(string text)
		{
			if (text == null)
			{
				throw new FormatException("No JSON text.");
			}
			int i = 0;
			object? result = Value(text, ref i, 0);
			Skip(text, ref i);
			if (i != text.Length)
			{
				throw new FormatException("Unexpected text at " + i + ".");
			}
			return result;
		}

		private static object? Value(string s, ref int i, int depth)
		{
			if (depth > 32)
			{
				throw new FormatException("Nesting too deep.");
			}
			Skip(s, ref i);
			if (i >= s.Length)
			{
				throw new FormatException("Unexpected end.");
			}
			char c = s[i];
			switch (c)
			{
			case '{':
				return Object(s, ref i, depth);
			case '[':
				return Array(s, ref i, depth);
			case '"':
				return String(s, ref i);
			case '-':
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				return Number(s, ref i);
			default:
				if (Literal(s, ref i, "true"))
				{
					return true;
				}
				if (Literal(s, ref i, "false"))
				{
					return false;
				}
				if (Literal(s, ref i, "null"))
				{
					return null;
				}
				throw new FormatException("Unexpected '" + c + "' at " + i + ".");
			}
		}

		private static Dictionary<string, object?> Object(string s, ref int i, int depth)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			i++;
			Skip(s, ref i);
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			while (true)
			{
				Skip(s, ref i);
				if (i >= s.Length || s[i] != '"')
				{
					throw new FormatException("Expected a key at " + i + ".");
				}
				string key = String(s, ref i);
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ':')
				{
					throw new FormatException("Expected ':' at " + i + ".");
				}
				i++;
				dictionary[key] = Value(s, ref i, depth + 1);
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			throw new FormatException("Expected ',' or '}' at " + i + ".");
		}

		private static List<object?> Array(string s, ref int i, int depth)
		{
			List<object> list = new List<object>();
			i++;
			Skip(s, ref i);
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			while (true)
			{
				list.Add(Value(s, ref i, depth + 1));
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			throw new FormatException("Expected ',' or ']' at " + i + ".");
		}

		private static string String(string s, ref int i)
		{
			StringBuilder stringBuilder = new StringBuilder();
			i++;
			while (i < s.Length)
			{
				char c = s[i++];
				if (c == '"')
				{
					return stringBuilder.ToString();
				}
				if (c < ' ')
				{
					throw new FormatException("Control character in a string.");
				}
				if (c != '\\')
				{
					stringBuilder.Append(c);
					continue;
				}
				if (i >= s.Length)
				{
					break;
				}
				char c2 = s[i++];
				switch (c2)
				{
				case '"':
					stringBuilder.Append('"');
					break;
				case '\\':
					stringBuilder.Append('\\');
					break;
				case '/':
					stringBuilder.Append('/');
					break;
				case 'b':
					stringBuilder.Append('\b');
					break;
				case 'f':
					stringBuilder.Append('\f');
					break;
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				case 't':
					stringBuilder.Append('\t');
					break;
				case 'u':
				{
					if (i + 4 > s.Length || !int.TryParse(s.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
					{
						throw new FormatException("Bad \\u escape.");
					}
					stringBuilder.Append((char)result);
					i += 4;
					break;
				}
				default:
					throw new FormatException("Bad escape \\" + c2 + ".");
				}
			}
			throw new FormatException("Unterminated string.");
		}

		private static double Number(string s, ref int i)
		{
			int num = i;
			if (s[i] == '-')
			{
				i++;
			}
			while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '.' || s[i] == 'e' || s[i] == 'E' || s[i] == '+' || s[i] == '-'))
			{
				i++;
			}
			if (!double.TryParse(s.Substring(num, i - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || double.IsInfinity(result))
			{
				throw new FormatException("Bad number at " + num + ".");
			}
			return result;
		}

		private static bool Literal(string s, ref int i, string word)
		{
			if (string.CompareOrdinal(s, i, word, 0, word.Length) != 0)
			{
				return false;
			}
			i += word.Length;
			return true;
		}

		private static void Skip(string s, ref int i)
		{
			while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r'))
			{
				i++;
			}
		}
	}
	public static class SecondaryCooldown
	{
		public const string Capability = "meadworks.inscribe.secondary_cooldown";

		public static readonly Version Version = new Version(1, 0);

		public const float MinMultiplier = 0.5f;

		public static float Multiplier(float attackSpeed, float castSpeed)
		{
			float num = Clean(attackSpeed) + Clean(castSpeed);
			if (!(num <= 0f))
			{
				return Math.Max(0.5f, 1f / (1f + num));
			}
			return 1f;
		}

		private static float Clean(float share)
		{
			if (!float.IsNaN(share) && !float.IsInfinity(share))
			{
				return Math.Max(0f, share);
			}
			return 0f;
		}
	}
	public static class SocketsV1
	{
		public const string Capability = "meadworks.items.sockets";

		public static readonly Version Version = new Version(1, 0);

		public static Type ContractType => typeof(Tuple<Func<object, int>, Func<object, Tuple<string, string, string, string>[]>>);
	}
	public static class SalvageRuneV1
	{
		public const string Capability = "meadworks.crafting.salvage_rune";

		public static readonly Version Version = new Version(1, 0);

		public static Type ContractType => typeof(Tuple<Func<object, string>, Func<object, float>>);
	}
	public static class SalvageGemsV1
	{
		public const string Capability = "meadworks.crafting.salvage_gems";

		public static readonly Version Version = new Version(1, 0);

		public static Type ContractType => typeof(Tuple<Func<object, string[]>, Func<object, float>, Func<int, string>>);
	}
}
namespace ExtraPotions.Inscribe.Integration
{
	public enum InscribeModule
	{
		Rarity,
		Enhancement,
		Drops,
		Display,
		Cheats
	}
	public sealed class ModuleSource
	{
		public string Guid { get; }

		public string Name { get; }

		internal ModuleSource(string guid, string name)
		{
			Guid = guid;
			Name = name;
		}
	}
	public readonly struct StepAsideNotice
	{
		public string Key { get; }

		public string SourceNames { get; }

		public string SourceGuid { get; }

		public string ModuleId => Key.Substring("step_aside_".Length);

		public StepAsideNotice(string key, string sourceNames, string sourceGuid)
		{
			Key = key;
			SourceNames = sourceNames;
			SourceGuid = sourceGuid;
		}
	}
	public sealed class StepAsideDecision
	{
		public bool EliteCrafting { get; }

		public bool ItemEnhancements { get; }

		public bool ProductOff
		{
			get
			{
				if (EliteCrafting)
				{
					return ItemEnhancements;
				}
				return false;
			}
		}

		public bool Any
		{
			get
			{
				if (!EliteCrafting)
				{
					return ItemEnhancements;
				}
				return true;
			}
		}

		public bool RuneAndGearDrops => !EliteCrafting;

		public bool RarityDisplay => !EliteCrafting;

		public bool RunePrefabs => !EliteCrafting;

		public bool ScrollPrefabs => !ItemEnhancements;

		public bool StatPatchesAfterItemEnhancements
		{
			get
			{
				if (ItemEnhancements)
				{
					return !EliteCrafting;
				}
				return false;
			}
		}

		public IReadOnlyList<StepAsideNotice> Notices { get; }

		internal StepAsideDecision(bool eliteCrafting, bool itemEnhancements)
		{
			EliteCrafting = eliteCrafting;
			ItemEnhancements = itemEnhancements;
			List<StepAsideNotice> list = new List<StepAsideNotice>();
			if (ProductOff)
			{
				list.Add(new StepAsideNotice("step_aside_all", ModuleStepAside.EliteCrafting.Name + " and " + ModuleStepAside.ItemEnhancements.Name, ModuleStepAside.EliteCrafting.Guid));
			}
			else if (eliteCrafting)
			{
				list.Add(new StepAsideNotice("step_aside_rarity", ModuleStepAside.EliteCrafting.Name, ModuleStepAside.EliteCrafting.Guid));
			}
			else if (itemEnhancements)
			{
				list.Add(new StepAsideNotice("step_aside_enhancement", ModuleStepAside.ItemEnhancements.Name, ModuleStepAside.ItemEnhancements.Guid));
			}
			Notices = list;
		}

		public bool SteppedAside(InscribeModule module)
		{
			if (ProductOff)
			{
				return true;
			}
			return module switch
			{
				InscribeModule.Rarity => EliteCrafting, 
				InscribeModule.Enhancement => ItemEnhancements, 
				_ => false, 
			};
		}
	}
	public static class ModuleStepAside
	{
		public static readonly ModuleSource EliteCrafting = new ModuleSource("com.EliteCrafting", "EliteCrafting");

		public static readonly ModuleSource ItemEnhancements = new ModuleSource("ckforgame.ItemEnhancements", "ItemEnhancements");

		public static IReadOnlyList<ModuleSource> Sources { get; } = Array.AsReadOnly(new ModuleSource[2] { EliteCrafting, ItemEnhancements });

		public static StepAsideDecision Decide(IEnumerable<string> installedGuids)
		{
			if (installedGuids == null)
			{
				throw new ArgumentNullException("installedGuids");
			}
			HashSet<string> hashSet = new HashSet<string>(installedGuids.Where((string g) => g != null), StringComparer.Ordinal);
			return new StepAsideDecision(hashSet.Contains(EliteCrafting.Guid), hashSet.Contains(ItemEnhancements.Guid));
		}
	}
}
namespace ExtraPotions.Inscribe.Enhancement
{
	public readonly struct TierRange
	{
		public int Grade { get; }

		public float Min { get; }

		public float Max { get; }

		public int Decimals { get; }

		public TierRange(int grade, float min, float max, int decimals)
		{
			Grade = grade;
			Min = Math.Min(min, max);
			Max = Math.Max(min, max);
			Decimals = Math.Max(0, Math.Min(decimals, 6));
		}
	}
	public sealed class ConvertibleInscription
	{
		public TierRange Current { get; }

		public float Value { get; }

		public IReadOnlyList<TierRange> Higher { get; }

		public ConvertibleInscription(TierRange current, float value, IReadOnlyList<TierRange>? higher)
		{
			Current = current;
			Value = value;
			Higher = higher ?? Array.Empty<TierRange>();
		}
	}
	public readonly struct RaisedInscription
	{
		public int Index { get; }

		public int Grade { get; }

		public float Value { get; }

		public RaisedInscription(int index, int grade, float value)
		{
			Index = index;
			Grade = grade;
			Value = value;
		}
	}
	public sealed class ItemConversion
	{
		public int Level { get; }

		public int Steps { get; }

		public IReadOnlyList<RaisedInscription> Raised { get; }

		public int TiersRaised { get; }

		public int HoningRunes { get; }

		public string Marker => "v1;level=" + Level.ToString(CultureInfo.InvariantCulture) + ";steps=" + Steps.ToString(CultureInfo.InvariantCulture) + ";raised=" + TiersRaised.ToString(CultureInfo.InvariantCulture) + ";runes=" + HoningRunes.ToString(CultureInfo.InvariantCulture);

		internal ItemConversion(int level, int steps, IReadOnlyList<RaisedInscription> raised, int tiersRaised, int honingRunes)
		{
			Level = level;
			Steps = steps;
			Raised = raised;
			TiersRaised = tiersRaised;
			HoningRunes = honingRunes;
		}
	}
	public readonly struct ScrollConversion
	{
		public int HoningRunes { get; }

		public int Essence { get; }

		public bool IsEmpty
		{
			get
			{
				if (HoningRunes == 0)
				{
					return Essence == 0;
				}
				return false;
			}
		}

		public ScrollConversion(int honingRunes, int essence)
		{
			HoningRunes = honingRunes;
			Essence = essence;
		}
	}
	public static class EnhancementConversion
	{
		public const int MaxLevel = 20;

		public const int ScrollTiers = 4;

		public const int NinthsPerHoningRune = 9;

		public const int EssencePerNinth = 2;

		public const string MarkerVersion = "v1";

		public static IReadOnlyList<string> ScrollPrefabs { get; } = Array.AsReadOnly(new string[4] { "ScrollEnhance_Tier1", "ScrollEnhance_Tier2", "ScrollEnhance_Tier3", "ScrollEnhance_Tier4" });

		public static int ScrollTier(string? prefab)
		{
			for (int i = 0; i < ScrollPrefabs.Count; i++)
			{
				if (string.Equals(prefab, ScrollPrefabs[i], StringComparison.Ordinal))
				{
					return i + 1;
				}
			}
			return 0;
		}

		public static int Ninths(int tier)
		{
			if (tier >= 1 && tier <= 4)
			{
				return (int)Math.Pow(3.0, tier - 1);
			}
			return 0;
		}

		public static int TierForLevel(int level)
		{
			if (level > 5)
			{
				if (level > 10)
				{
					if (level > 15)
					{
						return 4;
					}
					return 3;
				}
				return 2;
			}
			return 1;
		}

		public static int ReadLevel(IReadOnlyDictionary<string, string>? data)
		{
			if (data == null || !data.TryGetValue("ValheimEnhancement_Level", out string value))
			{
				return 0;
			}
			if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return 0;
			}
			return Math.Min(Math.Max(result, 0), 20);
		}

		public static bool IsConverted(IReadOnlyDictionary<string, string>? data)
		{
			if (data != null && data.TryGetValue("ecf_enhancement_converted", out string value))
			{
				return !string.IsNullOrEmpty(value);
			}
			return false;
		}

		public static bool MayRun(bool moduleActive, bool authorityReady)
		{
			return moduleActive && authorityReady;
		}

		public static bool NeedsConversion(IReadOnlyDictionary<string, string>? data)
		{
			if (ReadLevel(data) > 0)
			{
				return !IsConverted(data);
			}
			return false;
		}

		public static int Steps(int level)
		{
			level = Math.Min(Math.Max(level, 0), 20);
			if (level == 0)
			{
				return 0;
			}
			int num = 0;
			for (int i = 1; i <= level; i++)
			{
				num += Ninths(TierForLevel(i));
			}
			return Math.Max(1, (num + 9 - 1) / 9);
		}

		public static ItemConversion PlanItem(int level, IReadOnlyList<ConvertibleInscription?>? inscriptions)
		{
			level = Math.Min(Math.Max(level, 0), 20);
			int num = Steps(level);
			IReadOnlyList<ConvertibleInscription> readOnlyList = inscriptions ?? Array.Empty<ConvertibleInscription>();
			int[] array = new int[readOnlyList.Count];
			int num2 = num;
			bool flag = true;
			while (num2 > 0 && flag)
			{
				flag = false;
				for (int i = 0; i < readOnlyList.Count; i++)
				{
					if (num2 <= 0)
					{
						break;
					}
					if (readOnlyList[i] != null && array[i] < readOnlyList[i].Higher.Count)
					{
						array[i]++;
						num2--;
						flag = true;
					}
				}
			}
			List<RaisedInscription> list = new List<RaisedInscription>();
			for (int j = 0; j < readOnlyList.Count; j++)
			{
				if (array[j] != 0)
				{
					ConvertibleInscription convertibleInscription = readOnlyList[j];
					TierRange to = convertibleInscription.Higher[array[j] - 1];
					list.Add(new RaisedInscription(j, to.Grade, Carry(convertibleInscription.Value, convertibleInscription.Current, to)));
				}
			}
			return new ItemConversion(level, num, list, num - num2, num2);
		}

		public static float Carry(float value, TierRange from, TierRange to)
		{
			double val = ((from.Max > from.Min) ? ((double)(value - from.Min) / (double)(from.Max - from.Min)) : 1.0);
			val = Math.Min(1.0, Math.Max(0.0, val));
			return (float)Math.Min(Math.Max(Math.Max(Math.Round((double)to.Min + val * (double)(to.Max - to.Min), to.Decimals, MidpointRounding.AwayFromZero), value), to.Min), to.Max);
		}

		public static ScrollConversion PlanScrolls(IReadOnlyList<int>? counts)
		{
			long num = 0L;
			int num2 = 1;
			while (counts != null && num2 <= Math.Min(counts.Count, 4))
			{
				num += (long)Math.Max(0, counts[num2 - 1]) * (long)Ninths(num2);
				num2++;
			}
			long val = num / 9;
			long num3 = num % 9 * 2;
			return new ScrollConversion((int)Math.Min(val, 2147483647L), (int)num3);
		}

		public static void Mark(IDictionary<string, string> data, ItemConversion conversion)
		{
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (conversion == null)
			{
				throw new ArgumentNullException("conversion");
			}
			data["ecf_enhancement_converted"] = conversion.Marker;
		}

		public static (int Level, int Steps, int Raised, int Runes)? ReadMarker(IReadOnlyDictionary<string, string>? data)
		{
			if (data == null || !data.TryGetValue("ecf_enhancement_converted", out string value) || string.IsNullOrEmpty(value))
			{
				return null;
			}
			string[] array = value.Split(';');
			if (array.Length == 0 || array[0] != "v1")
			{
				return null;
			}
			int result;
			Dictionary<string, int> values = (from p in array.Skip(1)
				select p.Split('=') into p
				where p.Length == 2
				select p).ToDictionary<string[], string, int>((string[] p) => p[0], (string[] p) => (!int.TryParse(p[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) ? (-1) : result, StringComparer.Ordinal);
			return (Get("level"), Get("steps"), Get("raised"), Get("runes"));
			int Get(string key)
			{
				if (!values.TryGetValue(key, out var value2))
				{
					return -1;
				}
				return value2;
			}
		}
	}
	public sealed class OpenContainerConversion
	{
		private int? waiting;

		public int? Waiting => waiting;

		public bool Opened(int id, bool owned)
		{
			waiting = (owned ? ((int?)null) : new int?(id));
			return owned;
		}

		public bool Frame(int? openId, bool owned)
		{
			if (!waiting.HasValue)
			{
				return false;
			}
			if (openId != waiting)
			{
				waiting = null;
				return false;
			}
			if (!owned)
			{
				return false;
			}
			waiting = null;
			return true;
		}

		public void Reset()
		{
			waiting = null;
		}
	}
}
namespace ExtraPotions.Inscribe.Conversion
{
	public enum ImportSource
	{
		EliteCraftingConfig
	}
	public enum ImportNoteKind
	{
		Skipped,
		Unknown,
		Clamped,
		Fallback
	}
	public sealed class ImportNote
	{
		public ImportNoteKind Kind { get; }

		public string Source { get; }

		public string Text { get; }

		internal ImportNote(ImportNoteKind kind, string source, string text)
		{
			Kind = kind;
			Source = source;
			Text = text;
		}

		public override string ToString()
		{
			return Kind.ToString() + " " + Source + ": " + Text;
		}
	}
	public sealed class ImportValue
	{
		public string SettingId { get; }

		public object Value { get; }

		public string Source { get; }

		internal ImportValue(string settingId, object value, string source)
		{
			SettingId = settingId;
			Value = value;
			Source = source;
		}
	}
	public sealed class ImportPlan
	{
		public ImportSource Source { get; }

		public IReadOnlyList<ImportValue> Values { get; }

		public IReadOnlyList<ImportNote> Notes { get; }

		public string? LogOverride { get; }

		internal ImportPlan(ImportSource source, IReadOnlyList<ImportValue> values, IReadOnlyList<ImportNote> notes, string? logOverride)
		{
			Source = source;
			Values = values;
			Notes = notes;
			LogOverride = logOverride;
		}

		public string Report()
		{
			return string.Join("\n", new string[1] { "Source: " + Source }.Concat(Values.Select((ImportValue v) => "Set " + v.SettingId + " = " + Format(v.Value) + " (from " + v.Source + ")")).Concat((LogOverride == null) ? ((IEnumerable<string>)Array.Empty<string>()) : ((IEnumerable<string>)new string[1] { "Log override " + LogOverride })).Concat(Notes.Select((ImportNote n) => n.ToString())));
		}

		internal static string Format(object value)
		{
			string text;
			if (!(value is float num))
			{
				if (value is bool)
				{
					if (!(bool)value)
					{
						return "false";
					}
					return "true";
				}
				text = Convert.ToString(value, CultureInfo.InvariantCulture);
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = num.ToString("R", CultureInfo.InvariantCulture);
			}
			return text;
		}
	}
	public readonly struct SourceEntry
	{
		public string Section { get; }

		public string Key { get; }

		public string Value { get; }

		public SourceEntry(string section, string key, string value)
		{
			Section = section;
			Key = key;
			Value = value;
		}
	}
	public static class SourceConfig
	{
		public static IReadOnlyList<SourceEntry> Parse(string? text)
		{
			List<SourceEntry> list = new List<SourceEntry>();
			string section = "";
			string[] array = (text ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2[0] == '#')
				{
					continue;
				}
				if (text2[0] == '[' && text2[text2.Length - 1] == ']')
				{
					section = text2.Substring(1, text2.Length - 2).Trim();
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string text3 = text2.Substring(0, num).Trim();
					if (text3.Length != 0)
					{
						list.Add(new SourceEntry(section, text3, text2.Substring(num + 1).Trim()));
					}
				}
			}
			return list;
		}
	}
	public readonly struct YamlCopy
	{
		public string From { get; }

		public string To { get; }

		public YamlCopy(string from, string to)
		{
			From = from;
			To = to;
		}
	}
	public static class SourceImport
	{
		private const string EcGeneral = "1 - General";

		private const string EcRunes = "2 - Runes";

		private const string EcDrops = "3 - Drops";

		private const string EcCommands = "4 - Commands";

		private const string EcDisplay = "5 - Display (per player)";

		private const string EcGlow = "6 - Ground glow (per player)";

		private const string EcDiagnostics = "7 - Diagnostics";

		private const string EcEcr = "8 - Elite Creatures Reborn";

		private static readonly Dictionary<string, InscribeSetting> Ec = new Dictionary<string, InscribeSetting>(StringComparer.Ordinal)
		{
			{
				"1 - General.Inscription effects",
				InscribeSettings.General.InscriptionEffects
			},
			{
				"2 - Runes.Modify equipped items",
				InscribeSettings.Runes.ModifyEquippedItems
			},
			{
				"2 - Runes.Confirm destructive runes",
				InscribeSettings.Runes.ConfirmDestructiveRunes
			},
			{
				"3 - Drops.Rune drops",
				InscribeSettings.Drops.RuneDrops
			},
			{
				"3 - Drops.Magic item drops",
				InscribeSettings.Drops.MagicItemDrops
			},
			{
				"4 - Commands.Read-only commands for everyone",
				InscribeSettings.Commands.ReadOnlyForEveryone
			},
			{
				"5 - Display (per player).Colored item names",
				InscribeSettings.Display.ColoredItemNames
			},
			{
				"5 - Display (per player).Tooltip detail",
				InscribeSettings.Display.TooltipDetail
			},
			{
				"5 - Display (per player).Show dormant inscriptions",
				InscribeSettings.Display.ShowDormant
			},
			{
				"6 - Ground glow (per player).Ground glow",
				InscribeSettings.Glow.GroundGlow
			},
			{
				"6 - Ground glow (per player).Glow intensity",
				InscribeSettings.Glow.Intensity
			},
			{
				"6 - Ground glow (per player).Glow range",
				InscribeSettings.Glow.Range
			},
			{
				"6 - Ground glow (per player).Glow max lights",
				InscribeSettings.Glow.MaxLights
			},
			{
				"6 - Ground glow (per player).Glow refresh seconds",
				InscribeSettings.Glow.RefreshSeconds
			},
			{
				"6 - Ground glow (per player).Glow runes",
				InscribeSettings.Glow.GlowRunes
			},
			{
				"6 - Ground glow (per player).Loot beam",
				InscribeSettings.Glow.LootBeam
			},
			{
				"8 - Elite Creatures Reborn.Synergy",
				InscribeSettings.Drops.WildbloodStarSynergy
			}
		};

		private static readonly Dictionary<string, string> EcSkips = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "1 - General.Lock Configuration", "Crucible decides who may change settings (C01)" },
			{ "2 - Runes.Runes from salvage", "EliteCrafting 0.8+ salvage runes are outside Inscribe's 0.7.0 gameplay" },
			{ "2 - Runes.Rune Table", "EliteCrafting 0.8+ Rune Table is outside Inscribe's 0.7.0 gameplay" },
			{ "2 - Runes.Gems and sockets", "EliteCrafting 0.8+ gems and sockets are outside Inscribe's 0.7.0 gameplay" }
		};

		private static readonly Dictionary<string, int> BiomeTiers = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Meadows", 1 },
			{ "BlackForest", 2 },
			{ "Swamp", 3 },
			{ "Mountain", 4 },
			{ "Plains", 5 },
			{ "Mistlands", 6 },
			{ "AshLands", 7 },
			{ "DeepNorth", 8 },
			{ "Ocean", 4 }
		};

		public static string FileName(ImportSource source)
		{
			if (source == ImportSource.EliteCraftingConfig)
			{
				return "com.EliteCrafting.cfg";
			}
			throw new ArgumentException("Unknown source.", "source");
		}

		public static int? BiomeTier(string biome)
		{
			if (biome == null || !BiomeTiers.TryGetValue(biome, out var value))
			{
				return null;
			}
			return value;
		}

		public static ImportPlan Plan(ImportSource source, string? text)
		{
			if (source != ImportSource.EliteCraftingConfig)
			{
				throw new ArgumentException("Unknown source.", "source");
			}
			List<ImportValue> values = new List<ImportValue>();
			List<ImportNote> list = new List<ImportNote>();
			Dictionary<string, InscribeSetting> ec = Ec;
			bool flag = false;
			bool flag2 = false;
			foreach (SourceEntry item in SourceConfig.Parse(text))
			{
				string text2 = item.Section + "." + item.Key;
				string value;
				InscribeSetting value2;
				if (source == ImportSource.EliteCraftingConfig && item.Section == "7 - Diagnostics")
				{
					if (item.Key == "Log rolls")
					{
						flag = Eq(item.Value, "true");
					}
					else if (item.Key == "Log effect rebuilds")
					{
						flag2 = Eq(item.Value, "true");
					}
					else
					{
						list.Add(new ImportNote(ImportNoteKind.Unknown, text2, "not an Inscribe setting"));
					}
				}
				else if (source == ImportSource.EliteCraftingConfig && EcSkips.TryGetValue(text2, out value))
				{
					list.Add(new ImportNote(ImportNoteKind.Skipped, text2, value));
				}
				else if (!ec.TryGetValue(text2, out value2))
				{
					list.Add(new ImportNote(ImportNoteKind.Unknown, text2, "not an Inscribe setting"));
				}
				else
				{
					Add(value2, item.Value, text2, values, list);
				}
			}
			string logOverride = (flag ? "inscribe=Verbose" : (flag2 ? "inscribe=Debug" : null));
			return new ImportPlan(source, values, list, logOverride);
		}

		public static IReadOnlyList<YamlCopy> YamlCopies(IEnumerable<string> fileNames)
		{
			List<YamlCopy> list = new List<YamlCopy>();
			foreach (string item in fileNames.OrderBy<string, string>((string n) => n, StringComparer.Ordinal))
			{
				if (!item.EndsWith(".yml", StringComparison.Ordinal))
				{
					continue;
				}
				string[] array = new string[2] { "economy", "inscriptions" };
				foreach (string text in array)
				{
					string text2 = "EliteCrafting_" + text;
					if (item.StartsWith(text2, StringComparison.Ordinal))
					{
						list.Add(new YamlCopy(item, "Inscribe." + text + item.Substring(text2.Length)));
					}
				}
			}
			return list;
		}

		public static string CopyHeader(string sourceName)
		{
			return "# Imported by Meadworks from " + sourceName + " (EliteCrafting 0.7.0 format 2); the source file is unchanged.";
		}

		private static void Add(InscribeSetting target, string text, string from, List<ImportValue> values, List<ImportNote> notes)
		{
			if (values.Any((ImportValue v) => v.SettingId == target.Id))
			{
				notes.Add(new ImportNote(ImportNoteKind.Skipped, from, "merged into " + target.Id + " (first value kept)"));
			}
			else
			{
				values.Add(new ImportValue(target.Id, Convert(target, text, from, notes), from));
			}
		}

		private static object Convert(InscribeSetting target, string text, string from, List<ImportNote> notes)
		{
			if (!(target is InscribeSetting<bool>))
			{
				if (!(target is InscribeSetting<float>))
				{
					if (!(target is InscribeSetting<int>))
					{
						if (target is InscribeSetting<string>)
						{
							return Checked(text);
						}
						if (target.ValueType.IsEnum && target.Choices.Any((string c) => Eq(c, text.Replace(" ", ""))))
						{
							return Enum.Parse(target.ValueType, text.Replace(" ", ""), ignoreCase: true);
						}
						return Fallback("unknown value \"" + text + "\"");
					}
					if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						return Checked(result);
					}
					if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || (double)result2 != Math.Floor(result2) || !(Math.Abs(result2) < 2.1474836E+09f))
					{
						return Fallback("unreadable \"" + text + "\"");
					}
					return Checked((int)result2);
				}
				if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) || float.IsNaN(result3) || float.IsInfinity(result3))
				{
					return Fallback("unreadable \"" + text + "\"");
				}
				return Checked(result3);
			}
			if (Eq(text, "true"))
			{
				return true;
			}
			if (Eq(text, "false"))
			{
				return false;
			}
			return Fallback("unreadable \"" + text + "\"");
			object Checked(object value)
			{
				object obj = target.NormalizeBoxed(value);
				if (!object.Equals(obj, value))
				{
					notes.Add(new ImportNote(ImportNoteKind.Clamped, from, ImportPlan.Format(value) + " changed to " + ImportPlan.Format(obj)));
				}
				return obj;
			}
			object Fallback(string why)
			{
				notes.Add(new ImportNote(ImportNoteKind.Fallback, from, why + "; Inscribe default " + ImportPlan.Format(target.DefaultValue)));
				return target.DefaultValue;
			}
		}

		private static bool Eq(string a, string b)
		{
			return string.Equals(a?.Trim(), b, StringComparison.OrdinalIgnoreCase);
		}
	}
}
namespace ExtraPotions.Inscribe.Config
{
	public enum SettingScope
	{
		Local,
		ServerAuthoritative,
		Cheat
	}
	public abstract class InscribeSetting
	{
		private static readonly IReadOnlyList<string> NoChoices = Array.Empty<string>();

		public string Section { get; }

		public string Key { get; }

		public string Id => Section + "." + Key;

		public SettingScope Scope { get; }

		public string Description { get; }

		public IReadOnlyList<string> Choices { get; }

		public abstract Type ValueType { get; }

		public abstract object DefaultValue { get; }

		public abstract object? SafeValue { get; }

		public abstract object? Minimum { get; }

		public abstract object? Maximum { get; }

		private protected InscribeSetting(string section, string key, SettingScope scope, string description, IReadOnlyList<string>? choices)
		{
			if (string.IsNullOrWhiteSpace(section) || section.Trim() != section)
			{
				throw new ArgumentException("Expected a trimmed section name.", "section");
			}
			if (string.IsNullOrWhiteSpace(key) || key.Trim() != key)
			{
				throw new ArgumentException("Expected a trimmed key.", "key");
			}
			if (!Enum.IsDefined(typeof(SettingScope), scope))
			{
				throw new ArgumentException("Unknown scope.", "scope");
			}
			if (string.IsNullOrWhiteSpace(description))
			{
				throw new ArgumentException("Expected a description.", "description");
			}
			Section = section;
			Key = key;
			Scope = scope;
			Description = description;
			Choices = choices ?? NoChoices;
		}

		public abstract object NormalizeBoxed(object? value);

		public override string ToString()
		{
			return Id + " (" + Scope.ToString() + ")";
		}

		public static InscribeSetting<bool> Toggle(string section, string key, bool defaultValue, SettingScope scope, string description, bool? safe = null)
		{
			return new InscribeSetting<bool>(section, key, scope, description, defaultValue, safe, null, (bool v) => v);
		}

		public static InscribeSetting<int> Integer(string section, string key, int defaultValue, int min, int max, SettingScope scope, string description)
		{
			if (min > max)
			{
				throw new ArgumentException("Minimum above maximum.", "min");
			}
			return new InscribeSetting<int>(section, key, scope, description, defaultValue, null, (min, max), (int v) => Math.Min(max, Math.Max(min, v)));
		}

		public static InscribeSetting<float> Number(string section, string key, float defaultValue, float min, float max, SettingScope scope, string description)
		{
			if (!(min <= max) || float.IsInfinity(min) || float.IsInfinity(max))
			{
				throw new ArgumentException("Expected a finite range.", "min");
			}
			return new InscribeSetting<float>(section, key, scope, description, defaultValue, null, (min, max), (float v) => (!float.IsNaN(v) && !float.IsInfinity(v)) ? Math.Min(max, Math.Max(min, v)) : defaultValue);
		}

		public static InscribeSetting<TEnum> Choice<TEnum>(string section, string key, TEnum defaultValue, SettingScope scope, string description) where TEnum : struct, Enum
		{
			return new InscribeSetting<TEnum>(section, key, scope, description, defaultValue, null, null, (TEnum v) => (!Enum.IsDefined(typeof(TEnum), v)) ? defaultValue : v, Enum.GetNames(typeof(TEnum)));
		}

		public static InscribeSetting<string> Text(string section, string key, string defaultValue, SettingScope scope, string description, Func<string, string> normalize)
		{
			if (normalize == null)
			{
				throw new ArgumentNullException("normalize");
			}
			return new InscribeSetting<string>(section, key, scope, description, defaultValue, null, null, normalize);
		}
	}
	public sealed class InscribeSetting<T> : InscribeSetting
	{
		private readonly Func<T, T> normalize;

		private readonly bool hasSafe;

		private readonly (T Min, T Max)? range;

		public T Default { get; }

		public T Safe { get; }

		public override Type ValueType => typeof(T);

		public override object DefaultValue => Default;

		public override object? SafeValue
		{
			get
			{
				if (!hasSafe)
				{
					return null;
				}
				return Safe;
			}
		}

		public override object? Minimum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Min;
			}
		}

		public override object? Maximum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Max;
			}
		}

		internal InscribeSetting(string section, string key, SettingScope scope, string description, T defaultValue, object? safe, (T, T)? range, Func<T, T> normalize, IReadOnlyList<string>? choices = null)
			: base(section, key, scope, description, choices)
		{
			this.normalize = normalize ?? throw new ArgumentNullException("normalize");
			this.range = range;
			if (!EqualityComparer<T>.Default.Equals(normalize(defaultValue), defaultValue))
			{
				throw new ArgumentException(base.Id + ": the default is outside its own rule.", "defaultValue");
			}
			Default = defaultValue;
			if (scope == SettingScope.Cheat)
			{
				if (safe == null)
				{
					throw new ArgumentException(base.Id + ": a cheat needs a safe value.", "safe");
				}
				if (!EqualityComparer<T>.Default.Equals(defaultValue, (T)safe))
				{
					throw new ArgumentException(base.Id + ": a cheat must default to its safe value.", "defaultValue");
				}
				Safe = (T)safe;
				hasSafe = true;
			}
			else
			{
				if (safe != null)
				{
					throw new ArgumentException(base.Id + ": only cheats have a safe value.", "safe");
				}
				Safe = defaultValue;
			}
		}

		public T Normalize(T value)
		{
			return normalize(value);
		}

		public override object NormalizeBoxed(object? value)
		{
			return (value is T value2) ? Normalize(value2) : Default;
		}
	}
	public enum ConfirmMode
	{
		HoldShift,
		Dialog,
		Off
	}
	public enum TooltipDetail
	{
		Compact,
		Standard,
		Full
	}
	public static class InscribeSettings
	{
		public static class General
		{
			public const string Section = "1 - General";

			public static readonly InscribeSetting<bool> Enabled = InscribeSetting.Toggle("1 - General", "Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Whole product. Off: items keep and show their state; nothing rolls, drops or applies.");

			public static readonly InscribeSetting<bool> RarityEnabled = InscribeSetting.Toggle("1 - General", "Rarity Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Item rarity, inscriptions and runes. Off: nothing new rolls or drops and inscriptions stop applying; existing item data is kept and shown.");

			public static readonly InscribeSetting<bool> InscriptionEffects = InscribeSetting.Toggle("1 - General", "Inscription Effects", defaultValue: true, SettingScope.ServerAuthoritative, "Off: inscriptions are kept and shown, but none applies.");

			public static readonly InscribeSetting<bool> AccessoryRarity = InscribeSetting.Toggle("1 - General", "Accessory Rarity", defaultValue: true, SettingScope.ServerAuthoritative, "Rings and belts (Mantle) and trinkets can drop with a rarity and take runes. Off: new ones stay Common and runes refuse them; inscriptions they already have are kept and still apply. Host or server; default on. For one kind only, set rolls: false on the ring, belt or trinket class in Inscribe.economy.yml.");
		}

		public static class Runes
		{
			public const string Section = "2 - Runes";

			public static readonly InscribeSetting<bool> ModifyEquippedItems = InscribeSetting.Toggle("2 - Runes", "Modify Equipped Items", defaultValue: true, SettingScope.ServerAuthoritative, "Runes may be applied to equipped items.");

			public static readonly InscribeSetting<ConfirmMode> ConfirmDestructiveRunes = InscribeSetting.Choice("2 - Runes", "Confirm Destructive Runes", ConfirmMode.HoldShift, SettingScope.Local, "How confirm-gated runes (Cleansing, Sealed) ask first: HoldShift, a yes/no Dialog, or Off.");

			public static readonly InscribeSetting<bool> RunesFromSalvage = InscribeSetting.Toggle("2 - Runes", "Runes From Salvage", defaultValue: true, SettingScope.ServerAuthoritative, "Salvaging an Uncommon, Rare or Epic item (Kindle's Salvage) may give back the Ascension Rune that raised it, one time in four. Legendary items give none.");

			public static readonly InscribeSetting<bool> RuneTable = InscribeSetting.Toggle("2 - Runes", "Rune Table", defaultValue: true, SettingScope.ServerAuthoritative, "The Rune Table (hammer, Crafting tab): holds runes, turns trophies into essence and applies runes; an essence chooses what the Ascension Rune adds. Off: it cannot be built or used; what it holds is kept.");

			public static readonly InscribeSetting<bool> BiomeEssences = InscribeSetting.Toggle("2 - Runes", "Biome Essences", defaultValue: true, SettingScope.ServerAuthoritative, "The Rune Table also offers the Mist (Mistlands), Ash (Ashlands) and Frost (Deep North) essences, which steer the Ascension Rune toward movement and stealth; fury, ammunition and thrown weapons; or warmth, food, meads and endurance. Off: only the first five essences are offered; essence in the pool and inscriptions already rolled are kept. Host or server; default on.");

			public static readonly InscribeSetting<bool> GemsAndSockets = InscribeSetting.Toggle("2 - Runes", "Gems And Sockets", defaultValue: true, SettingScope.ServerAuthoritative, "Weapons, staves, armor, shields and backpacks hold up to three sockets: the Dvergr Chisel cuts them and the eleven gems fill them. Off: the chisel and gems neither drop nor work; socketed gems keep their stats.");

			public static readonly InscribeSetting<float> GemRecoveryCommon = InscribeSetting.Number("2 - Runes", "Gem Recovery Common", 100f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a gem comes back when it is replaced in a Common item or the item is salvaged; otherwise the gem is lost. Host or server; default 100.");

			public static readonly InscribeSetting<float> GemRecoveryUncommon = InscribeSetting.Number("2 - Runes", "Gem Recovery Uncommon", 90f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a gem comes back when it is replaced in a Uncommon item or the item is salvaged; otherwise the gem is lost. Host or server; default 90.");

			public static readonly InscribeSetting<float> GemRecoveryRare = InscribeSetting.Number("2 - Runes", "Gem Recovery Rare", 75f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a gem comes back when it is replaced in a Rare item or the item is salvaged; otherwise the gem is lost. Host or server; default 75.");

			public static readonly InscribeSetting<float> GemRecoveryEpic = InscribeSetting.Number("2 - Runes", "Gem Recovery Epic", 50f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a gem comes back when it is replaced in a Epic item or the item is salvaged; otherwise the gem is lost. Host or server; default 50.");

			public static readonly InscribeSetting<float> GemRecoveryLegendary = InscribeSetting.Number("2 - Runes", "Gem Recovery Legendary", 25f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a gem comes back when it is replaced in a Legendary item or the item is salvaged; otherwise the gem is lost. Host or server; default 25.");

			public static InscribeSetting<float>[] GemRecovery => new InscribeSetting<float>[5] { GemRecoveryCommon, GemRecoveryUncommon, GemRecoveryRare, GemRecoveryEpic, GemRecoveryLegendary };
		}

		public static class Drops
		{
			public const string Section = "4 - Drops";

			public static readonly InscribeSetting<bool> RuneDrops = InscribeSetting.Toggle("4 - Drops", "Rune Drops", defaultValue: true, SettingScope.ServerAuthoritative, "Creatures and bosses drop runes.");

			public static readonly InscribeSetting<bool> MagicItemDrops = InscribeSetting.Toggle("4 - Drops", "Magic Item Drops", defaultValue: true, SettingScope.ServerAuthoritative, "Creatures and bosses drop gear of Uncommon rarity or better.");

			public static readonly InscribeSetting<bool> ChestDrops = InscribeSetting.Toggle("4 - Drops", "Chest Drops", defaultValue: true, SettingScope.ServerAuthoritative, "World chests roll runes and gear once.");

			public static readonly InscribeSetting<bool> WildbloodStarSynergy = InscribeSetting.Toggle("4 - Drops", "Wildblood Star Synergy", defaultValue: false, SettingScope.ServerAuthoritative, "Wildblood's stars raise Inscribe's drop chances.");

			public static readonly InscribeSetting<float> UncommonWeight = InscribeSetting.Number("4 - Drops", "Uncommon Drop Weight", 1f, 0f, 5f, SettingScope.ServerAuthoritative, "Multiplies how often dropped gear is Uncommon (green), against the drop tables. 1: as the tables say; 0: never. Host or server; the next drop uses it.");

			public static readonly InscribeSetting<float> RareWeight = InscribeSetting.Number("4 - Drops", "Rare Drop Weight", 1f, 0f, 5f, SettingScope.ServerAuthoritative, "Multiplies how often dropped gear is Rare (blue), against the drop tables. 1: as the tables say; 0: never. Host or server; the next drop uses it.");

			public static readonly InscribeSetting<float> EpicWeight = InscribeSetting.Number("4 - Drops", "Epic Drop Weight", 1f, 0f, 5f, SettingScope.ServerAuthoritative, "Multiplies how often dropped gear is Epic (purple), against the drop tables. 1: as the tables say; 0: never. Host or server; the next drop uses it.");

			public static readonly InscribeSetting<bool> LegendaryFromBosses = InscribeSetting.Toggle("4 - Drops", "Legendary From Wildblood Bosses", defaultValue: true, SettingScope.ServerAuthoritative, "A boss kill may drop one Legendary (gold) item, when Wildblood is installed. Legendary never drops from other creatures, chests, crafting or runes. Host or server.");

			public static readonly InscribeSetting<bool> LegendaryFromRaids = InscribeSetting.Toggle("4 - Drops", "Legendary From Wildblood Raids", defaultValue: true, SettingScope.ServerAuthoritative, "A won Wildblood raid may drop one Legendary (gold) item at the raid site. Host or server.");

			public static readonly InscribeSetting<float> LegendaryBossChance = InscribeSetting.Number("4 - Drops", "Legendary Boss Chance", 20f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a boss kill drops a Legendary item, while Legendary From Wildblood Bosses is on. Host or server.");

			public static readonly InscribeSetting<float> LegendaryRaidChance = InscribeSetting.Number("4 - Drops", "Legendary Raid Chance", 10f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent chance a won raid drops a Legendary item, while Legendary From Wildblood Raids is on. Host or server.");
		}

		public static class Commands
		{
			public const string Section = "5 - Commands";

			public static readonly InscribeSetting<bool> ReadOnlyForEveryone = InscribeSetting.Toggle("5 - Commands", "Read-Only Commands For Everyone", defaultValue: true, SettingScope.ServerAuthoritative, "Everyone may use inscribe inspect, stats, list and help.");
		}

		public static class Display
		{
			public const string Section = "6 - Display";

			public static readonly InscribeSetting<bool> ColoredItemNames = InscribeSetting.Toggle("6 - Display", "Colored Item Names", defaultValue: true, SettingScope.Local, "Item names show in their rarity color.");

			public static readonly InscribeSetting<TooltipDetail> TooltipDetail = InscribeSetting.Choice("6 - Display", "Tooltip Detail", ExtraPotions.Inscribe.Config.TooltipDetail.Standard, SettingScope.Local, "Tooltip block: Compact, Standard or Full.");

			public static readonly InscribeSetting<bool> ShowDormant = InscribeSetting.Toggle("6 - Display", "Show Dormant Inscriptions", defaultValue: true, SettingScope.Local, "Inscriptions whose definition is gone show grayed.");
		}

		public static class Glow
		{
			public const string Section = "7 - Ground Glow";

			public static readonly InscribeSetting<bool> GroundGlow = InscribeSetting.Toggle("7 - Ground Glow", "Ground Glow", defaultValue: true, SettingScope.Local, "Dropped Uncommon or better gear glows in its rarity color.");

			public static readonly InscribeSetting<float> Intensity = InscribeSetting.Number("7 - Ground Glow", "Glow Intensity", 1f, 0f, 3f, SettingScope.Local, "Brightness of the ground glow.");

			public static readonly InscribeSetting<float> Range = InscribeSetting.Number("7 - Ground Glow", "Glow Range", 2f, 0.5f, 6f, SettingScope.Local, "Light range in meters.");

			public static readonly InscribeSetting<int> MaxLights = InscribeSetting.Integer("7 - Ground Glow", "Glow Max Lights", 25, 0, 100, SettingScope.Local, "Only the nearest this many items hold a live light.");

			public static readonly InscribeSetting<float> RefreshSeconds = InscribeSetting.Number("7 - Ground Glow", "Glow Refresh Seconds", 1f, 0.25f, 5f, SettingScope.Local, "How often the nearest items are chosen again.");

			public static readonly InscribeSetting<bool> GlowRunes = InscribeSetting.Toggle("7 - Ground Glow", "Glow Runes", defaultValue: false, SettingScope.Local, "Dropped runes glow as well.");

			public static readonly InscribeSetting<bool> LootBeam = InscribeSetting.Toggle("7 - Ground Glow", "Loot Beam", defaultValue: true, SettingScope.Local, "Glowing dropped gear also shows a beam of light in its rarity color.");

			public static readonly InscribeSetting<bool> LegendaryGlint = InscribeSetting.Toggle("7 - Ground Glow", "Legendary Glint", defaultValue: true, SettingScope.Local, "Dropped Legendary gear also shows a small bronze and ivory glint on the ground. Other rarities never glint. This client only.");
		}

		public static class Interface
		{
			public const string Section = "8 - Interface";

			public static readonly InscribeSetting<bool> StationButton = InscribeSetting.Toggle("8 - Interface", "Station Button", defaultValue: true, SettingScope.Local, "Retained legacy preference for the removed station button; no effect.");

			public static readonly InscribeSetting<RecastFocus> RecastingFocus = InscribeSetting.Choice("8 - Interface", "Recasting Focus", RecastFocus.Unrestricted, SettingScope.Local, "Your saved Rune Table category for one new inscription: Unrestricted, Offense, Defense or Utility. Recasting still replaces all inscriptions at the same rune cost and uses the host's rules. Your choice is saved on this computer.");

			public static readonly InscribeSetting<RecastSide> RecastingSide = InscribeSetting.Choice("8 - Interface", "Recasting Side", RecastSide.Prefix, SettingScope.Local, "Your saved prefix or suffix choice for the focused inscription. Applies only when a Recasting category is selected; the other inscriptions still reroll normally. Your choice is saved on this computer.");
		}

		public static class Cheats
		{
			public const string Section = "9 - Cheats";

			public static readonly InscribeSetting<bool> ItemCommands = InscribeSetting.Toggle("9 - Cheats", "Item Commands", defaultValue: false, SettingScope.Cheat, "Cheat. Enables inscribe give, roll, reroll and cleanse console commands.", false);
		}

		private static readonly Lazy<InscribeSetting[]> all = new Lazy<InscribeSetting[]>(() => new InscribeSetting[41]
		{
			General.Enabled,
			General.RarityEn