Decompiled source of Bestiary v0.1.19

plugins/Bestiary/Bestiary.Core.dll

Decompiled 21 hours 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.Bestiary.Catalog;
using ExtraPotions.Bestiary.Config;
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.1.19.0")]
[assembly: AssemblyInformationalVersion("0.1.19")]
[assembly: AssemblyProduct("Bestiary.Core")]
[assembly: AssemblyTitle("Bestiary.Core")]
[assembly: AssemblyVersion("0.1.19.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.Bestiary
{
	public static class BestiaryCore
	{
		public const string ProductId = "com.meadworks.bestiary";

		public const string ProductName = "Bestiary";

		public const string Family = "Fang";

		public const string Version = "0.1.19";

		public const string GameBuild = "1.0.17";
	}
	public static class BestiaryKeys
	{
		public const string Disguised = "ecp_disguised";

		public const string MimicChest = "ecp_mimic_chest";

		public const string MimicSwap = "ecp_mimic_swap";

		public const string MimicOpen = "ecp_mimic_open";

		public const string RimeGiantClaimPrefix = "ecp_rimegiant_";

		public const string RimePlates = "ecp_rime_plates";

		public const string SlagCharredClaimPrefix = "mw_slagcharred_";

		public const string SlagPlates = "mw_slag_plates";

		public const string SlagFire = "mw_slag_fire";

		public const string JavelinThrown = "mw_javelin_thrown";

		public const string HookReady = "mw_hook_ready";

		public const string BogHookPull = "MW_BogHookPull";

		public const string SlagClaimAsk = "MW_SlagClaim";

		public const string SlagClaimAnswer = "MW_SlagClaimAnswer";

		public const string HeadsmanChamber = "ecp_hs_chamber";

		public const string KrakenPhase = "ecp_kraken_phase";

		public static bool IsRegionClaim(string key)
		{
			if (!string.IsNullOrEmpty(key))
			{
				if (!key.StartsWith("ecp_rimegiant_", StringComparison.Ordinal))
				{
					return key.StartsWith("mw_slagcharred_", StringComparison.Ordinal);
				}
				return true;
			}
			return false;
		}
	}
}
namespace ExtraPotions.Bestiary.Text
{
	public static class BestiaryTextKeys
	{
		public const string Namespace = "bestiary";

		public static readonly IReadOnlyList<string> Attacks = new string[7] { "item_ecp_cryptmimic_bite", "item_ecp_slinger_shot", "item_ecp_rimegiant_sweep", "item_ecp_rimegiant_slam", "item_ecp_rimegiant_throw", "item_ecp_headsman_axe", "item_ecp_skeletoncrossbow" };

		public static readonly IReadOnlyList<string> Ballista = new string[10] { "piece_ecp_boneballista", "piece_ecp_boneballista_description", "item_ecp_bonemissile", "item_ecp_bonemissile_description", "hud_ecp_bal_hold", "hud_ecp_bal_letgo", "hud_ecp_bal_loaded", "hud_ecp_bal_empty", "msg_ecp_bal_nomissiles", "msg_ecp_bal_behind" };

		public const string Inked = "se_ecp_inked";

		public const string InkedTooltip = "se_ecp_inked_tooltip";

		public const string NoticeStepAside = "notice_step_aside";

		public const string NoticeWorkshopStepAside = "notice_workshop_step_aside";

		public const string NoticePlatformUnsupported = "notice_platform_unsupported";

		public const string NoticeServerMissing = "notice_server_missing";

		public static IReadOnlyList<string> All { get; } = (from c in CreatureCatalog.All
			where c.Word != null
			select c.Word).Concat<string>(Attacks).Concat(Ballista).Concat(CompendiumCatalog.Keys)
			.Concat(ItemCatalog.All.SelectMany((ItemDefinition i) => new string[2]
			{
				i.Word,
				Description(i.Word)
			}))
			.Concat(new string[6] { "se_ecp_inked", "se_ecp_inked_tooltip", "notice_step_aside", "notice_workshop_step_aside", "notice_platform_unsupported", "notice_server_missing" })
			.ToArray();

		public static string Description(string itemWord)
		{
			return itemWord + "_description";
		}

		public static string Token(string key)
		{
			return "$meadworks_bestiary_" + key;
		}
	}
}
namespace ExtraPotions.Bestiary.Spawning
{
	public static class KitCreatures
	{
		public const string BogHook = "MW_BogHook";

		public const string Javelineer = "MW_FulingJavelineer";

		public const string SlagCharred = "MW_SlagCharred";

		public const string MarrowLantern = "MW_MarrowLantern";

		public const string Draugr = "Draugr";

		public const string Fuling = "Goblin";

		public const string Charred = "Charred_Melee";

		public const string DeepNorthSkeleton = "Skeleton_DeepNorth";

		public const string ShadowPerson = "ShadowPerson";

		public const int SpawnLevel = 1;
	}
	public static class WildShare
	{
		public static bool Replace(bool enabled, string wanted, string prefab, bool fromSpawner, bool eventSpawn, BiomeMask biomes, BiomeMask here, float sharePercent, double roll)
		{
			if (!enabled || fromSpawner || eventSpawn || prefab != wanted || sharePercent <= 0f)
			{
				return false;
			}
			if ((biomes & here) != BiomeMask.None)
			{
				return roll < (double)sharePercent / 100.0;
			}
			return false;
		}
	}
	public static class SlagRegions
	{
		public const int MinEdge = 40;

		public const float Apart = 1000f;

		private const uint Salt = 1397506375u;

		public static string ClaimKey(int region)
		{
			return "mw_slagcharred_" + region;
		}

		public static uint Hash24(int worldSeed, int region)
		{
			int num = (worldSeed * -1640531535) ^ (region * -2048144789) ^ 0x534C4147;
			int num2 = (num ^ (num >>> 16)) * 2146121005;
			int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
			return (uint)((num3 ^ (num3 >>> 16)) & 0xFFFFFF);
		}

		public static bool Holds(int worldSeed, int region, float regionsPercent)
		{
			if (regionsPercent <= 0f)
			{
				return false;
			}
			if (regionsPercent >= 100f)
			{
				return true;
			}
			return (float)Hash24(worldSeed, region) / 16777216f < regionsPercent / 100f;
		}

		public static bool Waiting(int region, int edgeCount, BiomeMask biome, BiomeMask biomes, bool holds, bool claimed)
		{
			if (region >= 0 && edgeCount >= 40 && (biomes & biome) != 0 && holds)
			{
				return !claimed;
			}
			return false;
		}
	}
	public static class SlagClaim
	{
		public const float NearMeters = 128f;

		public static bool Near(float meters)
		{
			if (meters >= 0f)
			{
				return meters <= 128f;
			}
			return false;
		}

		public static bool Take(bool eligible, bool senderNear)
		{
			return eligible && senderNear;
		}

		public static bool TryTake(ISet<string> claimed, string key, bool eligible, bool senderNear)
		{
			if (!Take(eligible && !claimed.Contains(key), senderNear))
			{
				return false;
			}
			claimed.Add(key);
			return true;
		}
	}
	public static class SlagPlates
	{
		public const int BurningTick = 5;

		public const int FreezingTick = 6;

		public const int PoisonTick = 7;

		public static float Through(int plates, int max, float armoredPercent)
		{
			if (max <= 0 || plates <= 0)
			{
				return 1f;
			}
			if (plates > max)
			{
				plates = max;
			}
			float num = Math.Max(0f, Math.Min(100f, armoredPercent)) / 100f;
			return 1f - (1f - num) * (float)plates / (float)max;
		}

		public static int AfterFire(int plates, float stored, float fire, float perPlate, out float storedLeft)
		{
			if (plates <= 0 || fire <= 0f || perPlate <= 0f)
			{
				storedLeft = ((plates <= 0) ? 0f : stored);
				return Math.Max(0, plates);
			}
			stored += fire;
			while (plates > 0 && stored >= perPlate)
			{
				stored -= perPlate;
				plates--;
			}
			storedLeft = ((plates == 0) ? 0f : stored);
			return plates;
		}

		public static bool Direct(int hitType)
		{
			if (hitType != 5 && hitType != 6)
			{
				return hitType != 7;
			}
			return false;
		}

		public static float Counted(bool weaponHit, float fire)
		{
			if (!weaponHit || !(fire > 0f))
			{
				return 0f;
			}
			return fire;
		}

		public static bool Shown(int index, int plates)
		{
			if (index >= 0)
			{
				return index < plates;
			}
			return false;
		}
	}
	public static class StarHits
	{
		public static float Scale(int level)
		{
			return 1f + (float)Math.Max(0, level - 1) * 0.5f;
		}

		public static float Damage(float baseDamage, int level)
		{
			return baseDamage * Scale(level);
		}
	}
	public static class HookCooldown
	{
		public static bool Ready(long nowTicks, long readyTicks)
		{
			return nowTicks >= readyTicks;
		}

		public static long Arm(long nowTicks, float intervalSeconds)
		{
			double num = Math.Max(0.0, intervalSeconds);
			return nowTicks + (long)Math.Round(num * 10000000.0);
		}
	}
	public static class HookPull
	{
		public const float Cap = 20f;

		public const float PointSlack = 3f;

		public static float Clamp(float meters, float pullSetting)
		{
			float val = Math.Min(20f, Math.Max(0f, pullSetting));
			return Math.Min(Math.Max(0f, meters), val);
		}

		public static bool Allow(bool ownsLivingHook, float hookToTarget, float range, float pointToHook)
		{
			if (ownsLivingHook && hookToTarget <= range)
			{
				return pointToHook <= 3f;
			}
			return false;
		}
	}
	public static class MarrowPlaces
	{
		private const uint Salt = 1296126546u;

		public static uint HashPlace(int worldSeed, int x, int z)
		{
			int num = (worldSeed * -1640531535) ^ (x * -2048144789) ^ (z * -1028477387) ^ 0x4D415252;
			int num2 = (num ^ (num >>> 16)) * 2146121005;
			int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
			return (uint)((num3 ^ (num3 >>> 16)) & 0xFFFFFF);
		}

		public static bool PlaceHolds(int worldSeed, int x, int z, float placesPercent)
		{
			if (placesPercent <= 0f)
			{
				return false;
			}
			if (placesPercent >= 100f)
			{
				return true;
			}
			return (float)HashPlace(worldSeed, x, z) / 16777216f < placesPercent / 100f;
		}

		public static bool ThisSpawn(bool enabled, string prefab, bool placeHolds, float chancePercent, double roll)
		{
			if (!enabled || !placeHolds || chancePercent <= 0f || prefab == "ShadowPerson")
			{
				return false;
			}
			if (prefab == "Skeleton_DeepNorth")
			{
				return roll < (double)chancePercent / 100.0;
			}
			return false;
		}
	}
	public static class PropFit
	{
		public readonly struct Axes
		{
			public readonly float X;

			public readonly float Y;

			public readonly float Z;

			public Axes(float x, float y, float z)
			{
				X = x;
				Y = y;
				Z = z;
			}
		}

		public const float MinAxis = 0.0001f;

		public const string StripJoint = "Joint";

		public const string StripBody = "Rigidbody";

		public const string StripRest = "Rest";

		public static readonly string[] StripPasses = new string[3] { "Joint", "Rigidbody", "Rest" };

		public static float WorldScale(float meters, float unitLength)
		{
			float num = Math.Abs(unitLength);
			if (!(num < 0.0001f))
			{
				return meters / num;
			}
			return meters;
		}

		public static float Axis(float wanted, float bone)
		{
			float num = ((!(Math.Abs(bone) < 0.0001f)) ? bone : ((bone < 0f) ? (-0.0001f) : 0.0001f));
			return wanted / num;
		}

		public static Axes Scale(float worldScale, Axes bone)
		{
			return new Axes(Axis(worldScale, bone.X), Axis(worldScale, bone.Y), Axis(worldScale, bone.Z));
		}

		public static Axes IntoBone(Axes worldMeters, Axes right, Axes up, Axes forward, Axes boneScale)
		{
			float wanted = worldMeters.X * right.X + worldMeters.Y * right.Y + worldMeters.Z * right.Z;
			float wanted2 = worldMeters.X * up.X + worldMeters.Y * up.Y + worldMeters.Z * up.Z;
			return new Axes(z: Axis(worldMeters.X * forward.X + worldMeters.Y * forward.Y + worldMeters.Z * forward.Z, boneScale.Z), x: Axis(wanted, boneScale.X), y: Axis(wanted2, boneScale.Y));
		}

		public static Axes Front(Axes extent, Axes center)
		{
			float num = Math.Abs(extent.X);
			float num2 = Math.Abs(extent.Y);
			float num3 = Math.Abs(extent.Z);
			if (num + num2 + num3 < 0.0001f)
			{
				return new Axes(0f, 0f, 0f);
			}
			int num4 = ((!(num <= num2) || !(num <= num3)) ? ((num2 <= num3) ? 1 : 2) : 0);
			float num5 = num4 switch
			{
				1 => center.Y, 
				0 => center.X, 
				_ => center.Z, 
			};
			float num6 = num4 switch
			{
				1 => num2, 
				0 => num, 
				_ => num3, 
			};
			float num7 = ((num6 < 0.0001f || Math.Abs(num5) <= num6 * 0.02f) ? (-1f) : ((num5 > 0f) ? 1f : (-1f)));
			return num4 switch
			{
				1 => new Axes(0f, num7, 0f), 
				0 => new Axes(num7, 0f, 0f), 
				_ => new Axes(0f, 0f, num7), 
			};
		}

		public static bool FacesNegative(Axes front)
		{
			if (!(front.X < 0f) && !(front.Y < 0f))
			{
				return front.Z < 0f;
			}
			return true;
		}

		public static int StripRank(string pass)
		{
			for (int i = 0; i < StripPasses.Length; i++)
			{
				if (StripPasses[i] == pass)
				{
					return i;
				}
			}
			return StripPasses.Length;
		}
	}
	public sealed class NameSet
	{
		private readonly HashSet<string> names;

		public int Count => names.Count;

		private NameSet(HashSet<string> names)
		{
			this.names = names;
		}

		public static NameSet Parse(string? text)
		{
			return new NameSet(new HashSet<string>(from n in (text ?? "").Split(',')
				select n.Trim() into n
				where n.Length > 0
				select n, StringComparer.Ordinal));
		}

		public bool Contains(string name)
		{
			return names.Contains(name);
		}
	}
	public static class SpawnRules
	{
		public const float KrakenShipRange = 150f;

		public const float ApartDistance = 1000f;

		public const int ChamberSalt = 1213418830;

		public static bool IsMimic(bool enabled, NameSet chests, string chestPrefab, float chancePercent, double roll)
		{
			if (enabled && chests.Contains(chestPrefab))
			{
				return roll < (double)chancePercent / 100.0;
			}
			return false;
		}

		public static bool IsSlinger(bool enabled, string prefab, bool fromNest, bool nestsAllowed, BiomeMask biomes, BiomeMask here, float sharePercent, double roll)
		{
			if (!enabled || (fromNest && !nestsAllowed) || prefab != "Greydwarf" || sharePercent <= 0f)
			{
				return false;
			}
			if ((biomes & here) != BiomeMask.None)
			{
				return roll < (double)sharePercent / 100.0;
			}
			return false;
		}

		public static bool KrakenMaySpawn(bool enabled, float depth, float minDepth, bool crewedShipNear, bool anotherKrakenNear)
		{
			if (enabled && depth >= minDepth && crewedShipNear)
			{
				return !anotherKrakenNear;
			}
			return false;
		}

		public static bool ExecutionerChamber(int dungeonSeed, float chambersPercent)
		{
			return new Random(dungeonSeed ^ 0x48534D4E).NextDouble() < (double)chambersPercent / 100.0;
		}
	}
	public sealed class SkeletonSwitches
	{
		private readonly IReadOnlyDictionary<string, bool> titles;

		public bool Master { get; }

		public bool Crossbowman { get; }

		public bool CrossbowmanSpawns
		{
			get
			{
				if (Master)
				{
					return Crossbowman;
				}
				return false;
			}
		}

		public SkeletonSwitches(bool master, IReadOnlyDictionary<string, bool> titles, bool crossbowman)
		{
			Master = master;
			this.titles = titles;
			Crossbowman = crossbowman;
		}

		public bool Spawns(string title)
		{
			bool value = default(bool);
			return Master && titles.TryGetValue(title, out value) && value;
		}
	}
	public static class SkeletonDraw
	{
		public const string Archer = "Skeleton";

		public const string NoArcher = "Skeleton_NoArcher";

		private static readonly string[] Titles = new string[7] { "Cutthroat", "Swordsman", "Axeman", "Bonebreaker", "Spearman", "Halberdier", "Bowman" };

		public static IReadOnlyList<string> Candidates(string source, SkeletonSwitches switches)
		{
			List<string> list = new List<string>();
			bool flag = source == "Skeleton";
			if (!flag && source != "Skeleton_NoArcher")
			{
				return list;
			}
			string[] titles = Titles;
			foreach (string text in titles)
			{
				if ((flag || text != "Bowman") && switches.Spawns(text))
				{
					list.Add("ECP_Skeleton" + text);
				}
			}
			if (flag && switches.CrossbowmanSpawns)
			{
				list.Add("ECP_SkeletonCrossbowman");
			}
			return list;
		}

		public static int Slots(string source, SkeletonSwitches switches)
		{
			return Candidates(source, switches).Count + 1;
		}

		public static string? Instead(string source, SkeletonSwitches switches, Func<int, int> pick)
		{
			IReadOnlyList<string> readOnlyList = Candidates(source, switches);
			if (readOnlyList.Count == 0)
			{
				return null;
			}
			int num = pick(readOnlyList.Count + 1);
			if (num < 0 || num >= readOnlyList.Count)
			{
				return null;
			}
			return readOnlyList[num];
		}
	}
	public static class RimeMountains
	{
		public const int MinEdge = 40;

		public static string ClaimKey(int region)
		{
			return "ecp_rimegiant_" + region;
		}

		public static bool Eligible(int edgeCount, BiomeMask biome, BiomeMask biomes)
		{
			if (edgeCount >= 40)
			{
				return (biomes & biome) != 0;
			}
			return false;
		}

		public static uint Hash24(int worldSeed, int region)
		{
			int num = (worldSeed * -1640531535) ^ (region * -2048144789);
			int num2 = (num ^ (num >>> 16)) * 2146121005;
			int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
			return (uint)((num3 ^ (num3 >>> 16)) & 0xFFFFFF);
		}

		public static float Share(int worldSeed, int region)
		{
			return (float)Hash24(worldSeed, region) / 16777216f;
		}

		public static bool Holds(int worldSeed, int region, float mountainsPercent)
		{
			return Share(worldSeed, region) < mountainsPercent / 100f;
		}

		public static bool Waiting(int region, int edgeCount, BiomeMask biome, BiomeMask biomes, bool holds, bool claimed)
		{
			if (region >= 0 && Eligible(edgeCount, biome, biomes) && holds)
			{
				return !claimed;
			}
			return false;
		}
	}
	public static class SpearPool
	{
		private static readonly string[] Order = new string[3] { "GoblinSpear", "SpearFlint", "SpearWood" };

		public static IReadOnlyList<string> Candidates => Order;

		public static string[] For(Func<string, bool>? present)
		{
			if (present == null)
			{
				return Array.Empty<string>();
			}
			for (int i = 0; i < Order.Length; i++)
			{
				string text = Order[i];
				if (present(text))
				{
					return new string[1] { text };
				}
			}
			return Array.Empty<string>();
		}
	}
	public static class StandInProps
	{
		public readonly struct Grip
		{
			public readonly float Right;

			public readonly float Up;

			public readonly float Forward;

			public Grip(float right, float up, float forward)
			{
				Right = right;
				Up = up;
				Forward = forward;
			}
		}

		private static readonly string[] None = Array.Empty<string>();

		private static readonly string[] Hook = new string[3] { "PickaxeAntler", "AxeFlint", "FishingRod" };

		private static readonly string[] Plate = new string[3] { "ShieldBronzeBuckler", "ShieldIronBuckler", "ArmorIronChest" };

		private static readonly string[] Lantern = new string[3] { "Lantern_DN", "Lantern", "piece_dvergr_lantern" };

		public const float HookMeters = 1.2f;

		public const float LanternMeters = 0.5f;

		public const float PlateMeters = 0.45f;

		public static string[] Names(string creature)
		{
			return creature switch
			{
				"MW_BogHook" => Hook, 
				"MW_SlagCharred" => Plate, 
				"MW_MarrowLantern" => Lantern, 
				_ => None, 
			};
		}

		public static int Copies(string creature)
		{
			if (creature == "MW_SlagCharred")
			{
				return 3;
			}
			if (creature == "MW_FulingJavelineer")
			{
				return 0;
			}
			return 1;
		}

		public static float Meters(string creature)
		{
			return creature switch
			{
				"MW_BogHook" => 1.2f, 
				"MW_MarrowLantern" => 0.5f, 
				"MW_SlagCharred" => 0.45f, 
				_ => 1f, 
			};
		}

		public static Grip HoldGrip(string creature, bool onBody)
		{
			if (onBody)
			{
				return new Grip(0.35f, 1.3f, 0.25f);
			}
			if (creature == "MW_MarrowLantern")
			{
				return new Grip(0.02f, -0.05f, 0.06f);
			}
			return new Grip(0f, 0f, 0f);
		}

		public static Grip PlateGrip(int index, bool onBody)
		{
			Grip result = index switch
			{
				2 => new Grip(0.79f, 0.29f, 0.12f), 
				1 => new Grip(-0.79f, 0.29f, 0.12f), 
				_ => new Grip(0f, 0.31f, 0.3f), 
			};
			if (!onBody)
			{
				return result;
			}
			return new Grip(result.Right, result.Up + 1.55f, result.Forward);
		}

		public static string? Choose(string creature, bool allowsUi, Func<string, bool> registered)
		{
			if (!allowsUi || registered == null)
			{
				return null;
			}
			string[] array = Names(creature);
			for (int i = 0; i < array.Length; i++)
			{
				if (registered(array[i]))
				{
					return array[i];
				}
			}
			return null;
		}
	}
	public sealed class StandInTable
	{
		private readonly Dictionary<int, List<int>> replaces = new Dictionary<int, List<int>>();

		private readonly Dictionary<int, List<int>> standIns = new Dictionary<int, List<int>>();

		public bool IsEmpty => replaces.Count == 0;

		public void Add(int standIn, params int[] games)
		{
			foreach (int num in games)
			{
				AddOnce(replaces, standIn, num);
				AddOnce(standIns, num, standIn);
			}
		}

		public bool CountsFor(int creature, bool tamed, IEnumerable<int> areaPrefabs)
		{
			if (tamed || !replaces.TryGetValue(creature, out List<int> value))
			{
				return false;
			}
			foreach (int areaPrefab in areaPrefabs)
			{
				if (value.Contains(areaPrefab))
				{
					return true;
				}
			}
			return false;
		}

		public int Count(int game, IEnumerable<(int Prefab, bool Event)> objects, bool eventCreaturesOnly)
		{
			if (!standIns.TryGetValue(game, out List<int> value))
			{
				return 0;
			}
			int num = 0;
			foreach (var (item, flag) in objects)
			{
				if (value.Contains(item) && (!eventCreaturesOnly || flag))
				{
					num++;
				}
			}
			return num;
		}

		public void Clear()
		{
			replaces.Clear();
			standIns.Clear();
		}

		private static void AddOnce(Dictionary<int, List<int>> map, int key, int value)
		{
			if (!map.TryGetValue(key, out List<int> value2))
			{
				value2 = (map[key] = new List<int>());
			}
			if (!value2.Contains(value))
			{
				value2.Add(value);
			}
		}
	}
}
namespace ExtraPotions.Bestiary.Interop
{
	public readonly struct GearEntry
	{
		public string Prefab { get; }

		public string ItemClass { get; }

		public int Level { get; }

		public GearEntry(string prefab, string itemClass, int level)
		{
			Prefab = prefab;
			ItemClass = itemClass;
			Level = level;
		}
	}
	public readonly struct LootProfile
	{
		public string Prefab { get; }

		public string Json { get; }

		public LootProfile(string prefab, string json)
		{
			Prefab = prefab;
			Json = json;
		}
	}
	public static class InscribeTable
	{
		private const int BlackForest = 2;

		private const int Mountain = 4;

		private const int Ocean = 4;

		private static readonly (BiomeMask Biome, int Level)[] Levels = new(BiomeMask, int)[9]
		{
			(BiomeMask.Meadows, 1),
			(BiomeMask.BlackForest, 2),
			(BiomeMask.Swamp, 3),
			(BiomeMask.Mountain, 4),
			(BiomeMask.Ocean, 4),
			(BiomeMask.Plains, 5),
			(BiomeMask.Mistlands, 6),
			(BiomeMask.AshLands, 7),
			(BiomeMask.DeepNorth, 8)
		};

		private static readonly (string Weapon, string Class)[] ArsenalClasses = new(string, string)[7]
		{
			("Dagger", "knife"),
			("Sword", "sword_1h"),
			("Axe", "axe_1h"),
			("Mace", "mace_1h"),
			("Spear", "spear"),
			("Atgeir", "atgeir"),
			("Bow", "bow")
		};

		public static IReadOnlyList<GearEntry> Gear { get; } = ArsenalClasses.Select(((string Weapon, string Class) a) => new GearEntry("ECP_Bone" + a.Weapon, a.Class, 2)).Concat(new GearEntry[3]
		{
			new GearEntry("ECP_BoneCrossbow", "crossbow", 2),
			new GearEntry("ECP_ExecutionerGreataxe", "battleaxe", 2),
			new GearEntry("ECP_ShieldKraken", "shield", 4)
		}).ToArray();

		public static int LowestLevel(BiomeMask biomes, int none)
		{
			int num = 0;
			(BiomeMask, int)[] levels = Levels;
			for (int i = 0; i < levels.Length; i++)
			{
				var (biomeMask, num2) = levels[i];
				if ((biomes & biomeMask) != BiomeMask.None && (num == 0 || num2 < num))
				{
					num = num2;
				}
			}
			if (num <= 0)
			{
				return none;
			}
			return num;
		}

		public static IEnumerable<LootProfile> CreatureLoot(BiomeMask rimeGiantBiomes, BiomeMask slingerBiomes)
		{
			yield return Boss("ECP_Kraken", 4, 3, "serpent", 100);
			yield return Boss("ECP_RimeGiant", LowestLevel(rimeGiantBiomes, 4), 3, "ascension", 50);
			yield return Boss("ECP_Headsman", 2, 2, "ascension", 25);
			yield return Entry("ECP_HeadsmanSkeleton", "\"multiplier\": 0");
			yield return Entry("ECP_CryptMimic", "\"rune_multiplier\": 6, \"gear_multiplier\": 8");
			yield return Entry("ECP_GreydwarfSlinger", Tier(LowestLevel(slingerBiomes, 2)));
			foreach (CreatureDefinition arsenalSkeleton in CreatureCatalog.ArsenalSkeletons)
			{
				yield return Entry(arsenalSkeleton.Prefab, Tier(2));
			}
			yield return Entry("ECP_SkeletonCrossbowman", Tier(2));
			yield return Entry("MW_BogHook", Tier(3));
			yield return Entry("MW_FulingJavelineer", Tier(5));
			yield return Entry("MW_SlagCharred", Tier(7));
			yield return Entry("MW_MarrowLantern", Tier(8));
		}

		private static LootProfile Boss(string prefab, int tier, int runes, string bonus, int chance)
		{
			return Entry(prefab, $"\"boss\": true, {Tier(tier)}, \"rune_rolls\": {runes}, \"gear_rolls\": 1, " + $"\"bonus\": [ {{ \"rune\": \"{bonus}\", \"chance\": {chance}, \"amount\": 1 }} ]");
		}

		private static string Tier(int tier)
		{
			return $"\"tier\": {tier}";
		}

		private static LootProfile Entry(string prefab, string fields)
		{
			return new LootProfile(prefab, "{ \"prefab\": \"" + prefab + "\", " + fields + " }");
		}
	}
	public static class CapabilityIds
	{
		public const string CreatureRules = "meadworks.creatures.rules";

		public const string Disguise = "meadworks.creatures.disguise";

		public const string ItemClasses = "meadworks.items.classes";

		public const string BestiaryWorld = "meadworks.bestiary.world";

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

		public static IReadOnlyList<string> All { get; } = new string[4] { "meadworks.creatures.rules", "meadworks.creatures.disguise", "meadworks.items.classes", "meadworks.bestiary.world" };
	}
	public sealed class CreatureDefaults
	{
		public string Prefab { get; }

		public bool Boss { get; }

		public bool MiniBoss { get; }

		public bool Worthless { get; }

		public string? Body { get; }

		public int CloakedReveal { get; }

		public IReadOnlyList<string> NeverTraits { get; }

		public bool NeverDevoured { get; }

		public IReadOnlyList<string> ExcludedAspects { get; }

		public CreatureDefaults(string prefab, bool boss = false, bool miniBoss = false, bool worthless = false, string? body = null, int cloakedReveal = 0, IReadOnlyList<string>? neverTraits = null, bool neverDevoured = false, IReadOnlyList<string>? excludedAspects = null)
		{
			Prefab = prefab;
			Boss = boss;
			MiniBoss = miniBoss;
			Worthless = worthless;
			Body = body;
			CloakedReveal = cloakedReveal;
			NeverTraits = neverTraits ?? Array.Empty<string>();
			NeverDevoured = neverDevoured;
			ExcludedAspects = excludedAspects ?? Array.Empty<string>();
		}
	}
	public static class WildbloodDefaults
	{
		public static IReadOnlyList<CreatureDefaults> All { get; } = new CreatureDefaults[8]
		{
			new CreatureDefaults("ECP_Kraken", boss: true, miniBoss: false, worthless: false, null, 0, null, neverDevoured: false, new string[4] { "Twin", "Phantom", "Tethered", "Brutal" }),
			new CreatureDefaults("ECP_RimeGiant", boss: false, miniBoss: false, worthless: false, "large", 15, new string[2] { "Gilded", "Relentless" }, neverDevoured: true),
			new CreatureDefaults("ECP_Headsman", boss: false, miniBoss: true),
			new CreatureDefaults("ECP_HeadsmanSkeleton", boss: false, miniBoss: false, worthless: true),
			new CreatureDefaults("MW_BogHook"),
			new CreatureDefaults("MW_FulingJavelineer"),
			new CreatureDefaults("MW_SlagCharred"),
			new CreatureDefaults("MW_MarrowLantern", boss: false, miniBoss: false, worthless: false, null, 12)
		};

		public static CreatureDefaults? Find(string prefab)
		{
			return All.FirstOrDefault((CreatureDefaults d) => d.Prefab == prefab);
		}

		public static string Json(CreatureDefaults d, int marrowReveal = 12)
		{
			int num = ((d.Prefab == "MW_MarrowLantern") ? marrowReveal : d.CloakedReveal);
			StringBuilder stringBuilder = new StringBuilder("{\"prefab\":").Append(Quote(d.Prefab));
			if (d.Boss)
			{
				stringBuilder.Append(",\"boss\":true");
			}
			if (d.MiniBoss)
			{
				stringBuilder.Append(",\"mini_boss\":true");
			}
			if (d.Worthless)
			{
				stringBuilder.Append(",\"worthless\":true");
			}
			if (d.Body != null)
			{
				stringBuilder.Append(",\"body\":").Append(Quote(d.Body));
			}
			if (num > 0 || (d.Prefab == "MW_MarrowLantern" && num == 0))
			{
				stringBuilder.Append(",\"cloaked_reveal\":").Append(num);
			}
			if (d.NeverTraits.Count > 0)
			{
				stringBuilder.Append(",\"never_traits\":[").Append(string.Join(",", d.NeverTraits.Select(Quote))).Append(']');
			}
			if (d.NeverDevoured)
			{
				stringBuilder.Append(",\"never_devoured\":true");
			}
			if (d.ExcludedAspects.Count > 0)
			{
				stringBuilder.Append(",\"excluded_aspects\":[").Append(string.Join(",", d.ExcludedAspects.Select(Quote))).Append(']');
			}
			return stringBuilder.Append('}').ToString();
		}

		private static string Quote(string text)
		{
			return "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
		}
	}
	public static class Disguise
	{
		public static bool IsDisguised(string prefab, bool disguisedFlag)
		{
			return prefab == "ECP_CryptMimic" && disguisedFlag;
		}
	}
	public enum InteropOutcome
	{
		Absent,
		Registered,
		Partial,
		Failed
	}
	public static class InteropCalls
	{
		public static InteropOutcome RegisterDefaults(Func<string, string, bool>? register, int marrowReveal = 12)
		{
			if (register == null)
			{
				return InteropOutcome.Absent;
			}
			return Send(((IEnumerable<CreatureDefaults>)WildbloodDefaults.All).Select((Func<CreatureDefaults, Func<bool>>)((CreatureDefaults d) => () => register(d.Prefab, WildbloodDefaults.Json(d, marrowReveal)))));
		}

		public static InteropOutcome RegisterInscribe(Tuple<Func<string, string, bool>, Func<string, int, bool>, Func<string, bool>>? classes, BiomeMask rimeGiantBiomes, BiomeMask slingerBiomes)
		{
			if (classes == null || classes.Item1 == null || classes.Item2 == null || classes.Item3 == null)
			{
				return InteropOutcome.Absent;
			}
			return Send(((IEnumerable<GearEntry>)InscribeTable.Gear).Select((Func<GearEntry, Func<bool>>)((GearEntry g) => () => classes.Item1(g.ItemClass, g.Prefab) & classes.Item2(g.Prefab, g.Level))).Concat(InscribeTable.CreatureLoot(rimeGiantBiomes, slingerBiomes).Select((Func<LootProfile, Func<bool>>)((LootProfile p) => () => classes.Item3(p.Json)))));
		}

		private static InteropOutcome Send(IEnumerable<Func<bool>> calls)
		{
			int num = 0;
			try
			{
				foreach (Func<bool> call in calls)
				{
					if (!call())
					{
						num++;
					}
				}
			}
			catch (Exception)
			{
				return InteropOutcome.Failed;
			}
			if (num != 0)
			{
				return InteropOutcome.Partial;
			}
			return InteropOutcome.Registered;
		}
	}
}
namespace ExtraPotions.Bestiary.Fixes
{
	public enum CryptSwapStep
	{
		Retry,
		Swap,
		Fill
	}
	public static class CryptSwapRules
	{
		public static CryptSwapStep Next(bool viewValid, bool owner, bool prefabReady, bool buildSettled, bool mimicsOn)
		{
			if (!viewValid || !owner)
			{
				return CryptSwapStep.Retry;
			}
			if (!mimicsOn)
			{
				return CryptSwapStep.Fill;
			}
			if (prefabReady)
			{
				return CryptSwapStep.Swap;
			}
			if (!buildSettled)
			{
				return CryptSwapStep.Retry;
			}
			return CryptSwapStep.Fill;
		}
	}
	public static class BallistaGrant
	{
		public const float UseRange = 3.5f;

		public const float Behind = 0f;

		public static bool InPlace(float distance, float localForward)
		{
			if (distance <= 3.5f)
			{
				return localForward <= 0f;
			}
			return false;
		}

		public static bool AcceptRequest(long senderPlayerId, long claimedPlayerId, float distance, float localForward, bool heldBySomeoneElse)
		{
			if (senderPlayerId != 0L && senderPlayerId == claimedPlayerId && InPlace(distance, localForward))
			{
				return !heldBySomeoneElse;
			}
			return false;
		}

		public static bool AcceptRelease(long senderPlayerId, long claimedPlayerId, long holderId)
		{
			if (senderPlayerId != 0L && senderPlayerId == claimedPlayerId)
			{
				return holderId == claimedPlayerId;
			}
			return false;
		}

		public static bool AcceptGrant(bool thisClientAsked)
		{
			return thisClientAsked;
		}
	}
	public static class SharedAttackRules
	{
		public const float SpinAngle = 360f;

		public const int SpinChain = 1;

		private static readonly HashSet<string> Runtime = new HashSet<string>(StringComparer.Ordinal)
		{
			"m_time", "m_currentAttackCainLevel", "m_nextAttackChainLevel", "m_character", "m_baseAI", "m_body", "m_zanim", "m_animEvent", "m_weapon", "m_visEquipment",
			"m_lastUsedAmmo", "m_attackDrawPercentage", "m_wasInAttack", "m_abortAttack", "m_attackTowardsCameraDir", "m_projectileAttackStarted", "m_projectileFireTimer", "m_projectileBurstsFired", "m_ammoItem", "m_attackDone",
			"m_isAttached", "m_attachTarget", "m_attachOffset", "m_attachDistance", "m_attachHitPoint", "m_detachTimer"
		};

		public static bool ShareAttackObjects => false;

		public static bool IsRuntime(string field)
		{
			return Runtime.Contains(field);
		}

		public static float SwingAngle(int chainLevel, float configuredAngle)
		{
			if (chainLevel != 1)
			{
				return configuredAngle;
			}
			return 360f;
		}
	}
	public static class BossCountRules
	{
		public static bool MayWrite(bool isServer)
		{
			return isServer;
		}

		public static float Decrement(float serverCount)
		{
			float num = serverCount - 1f;
			if (!(num < 0f))
			{
				return num;
			}
			return 0f;
		}

		public static bool Accept(bool isServer, bool senderOwns, bool alreadyReleased, bool counted, bool isKraken)
		{
			return isServer && senderOwns && !alreadyReleased && counted && isKraken;
		}
	}
}
namespace ExtraPotions.Bestiary.Conversion
{
	public enum ImportNoteKind
	{
		Skipped,
		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 IReadOnlyList<ImportValue> Values { get; }

		public IReadOnlyList<ImportNote> Notes { get; }

		internal ImportPlan(IReadOnlyList<ImportValue> values, IReadOnlyList<ImportNote> notes)
		{
			Values = values;
			Notes = notes;
		}

		public string Report()
		{
			return string.Join("\n", new string[1] { "Source: com.EliteCreaturesPack.cfg" }.Concat(Values.Select((ImportValue v) => "Set " + v.SettingId + " = " + Format(v.Value) + " (from " + v.Source + ")")).Concat(Notes.Select((ImportNote n) => n.ToString())));
		}

		internal static string Format(object value)
		{
			string text;
			if (!(value is float num))
			{
				if (value is double num2)
				{
					return num2.ToString("R", CultureInfo.InvariantCulture);
				}
				if (value is bool)
				{
					if (!(bool)value)
					{
						return "false";
					}
					return "true";
				}
				if (value is BiomeMask mask)
				{
					return BiomeMasks.Format(mask);
				}
				text = Convert.ToString(value, CultureInfo.InvariantCulture);
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = num.ToString("R", CultureInfo.InvariantCulture);
			}
			return text;
		}
	}
	public static class SourceConfig
	{
		public static IReadOnlyList<(string Section, string Key, string Value)> Parse(string? text)
		{
			List<(string, string, string)> list = new List<(string, string, string)>();
			string item = "";
			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] == ']')
				{
					item = 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((item, text3, text2.Substring(num + 1).Trim()));
					}
				}
			}
			return list;
		}
	}
	public static class EcpImport
	{
		public const string FileName = "com.EliteCreaturesPack.cfg";

		private const string Lock = "1 - General.Lock Configuration";

		private static readonly Dictionary<string, string> Renamed = new Dictionary<string, string> { { "4 - Rime Giant.Armoured Damage", "4 - Rime Giant.Armored Damage" } };

		public static ImportPlan Plan(string? text)
		{
			List<ImportValue> list = new List<ImportValue>();
			List<ImportNote> list2 = new List<ImportNote>();
			foreach (var item4 in SourceConfig.Parse(text))
			{
				string item = item4.Section;
				string item2 = item4.Key;
				string item3 = item4.Value;
				string text2 = item + "." + item2;
				if (text2 == "1 - General.Lock Configuration")
				{
					list2.Add(new ImportNote(ImportNoteKind.Skipped, text2, "dropped; Crucible binds server-authoritative settings to the server"));
					continue;
				}
				string value;
				string text3 = (Renamed.TryGetValue(text2, out value) ? value : text2);
				SettingSpec settingSpec = BestiarySettings.Find(text3);
				if (settingSpec == null)
				{
					list2.Add(new ImportNote(ImportNoteKind.Skipped, text2, "no Bestiary setting"));
					continue;
				}
				object obj = Read(settingSpec, item3, text2, list2);
				if (obj != null)
				{
					list.Add(new ImportValue(text3, obj, text2));
				}
			}
			return new ImportPlan(list, list2);
		}

		private static object? Read(SettingSpec spec, string raw, string id, List<ImportNote> notes)
		{
			object obj = spec.Default;
			if (!(obj is bool))
			{
				if (!(obj is int))
				{
					if (!(obj is float))
					{
						if (obj is BiomeMask)
						{
							BiomeMask? biomeMask = BiomeMasks.Parse(raw);
							if (!biomeMask.HasValue)
							{
								return Fallback(notes, id, raw, "is not a biome list");
							}
							return biomeMask.Value;
						}
						return raw;
					}
					if (!float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result))
					{
						return Fallback(notes, id, raw, "is not a number");
					}
					return Clamp(spec, result, spec.Clamp(result), raw, id, notes);
				}
				if (!int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
				{
					return Fallback(notes, id, raw, "is not a number");
				}
				return Clamp(spec, result2, spec.Clamp(result2), raw, id, notes);
			}
			if (bool.TryParse(raw, out var result3))
			{
				return result3;
			}
			return Fallback(notes, id, raw, "is not true or false");
		}

		private static object Clamp<T>(SettingSpec spec, T value, T clamped, string raw, string id, List<ImportNote> notes)
		{
			if (!EqualityComparer<T>.Default.Equals(value, clamped))
			{
				notes.Add(new ImportNote(ImportNoteKind.Clamped, id, raw + " is outside " + ImportPlan.Format(spec.Minimum.Value) + " to " + ImportPlan.Format(spec.Maximum.Value) + "; " + ImportPlan.Format(clamped) + " used"));
			}
			return clamped;
		}

		private static object? Fallback(List<ImportNote> notes, string id, string raw, string why)
		{
			notes.Add(new ImportNote(ImportNoteKind.Fallback, id, "\"" + raw + "\" " + why + "; Bestiary's value kept"));
			return null;
		}
	}
	public readonly struct StepAsideDecision
	{
		public bool ProductYields { get; }

		public bool WorkshopYields { get; }

		public StepAsideDecision(bool productYields, bool workshopYields)
		{
			ProductYields = productYields;
			WorkshopYields = workshopYields;
		}
	}
	public static class StepAsideRules
	{
		public const string EcpGuid = "com.EliteCreaturesPack";

		public const string CreatureManagerGuid = "sighsorry.CreatureManager";

		public const string WorkshopId = "com.meadworks.bestiary.workshop";

		public static StepAsideDecision Decide(IEnumerable<string> installed, bool cmDomainsEmpty)
		{
			HashSet<string> hashSet = new HashSet<string>(installed ?? Enumerable.Empty<string>(), StringComparer.Ordinal);
			bool num = hashSet.Contains("com.EliteCreaturesPack");
			bool workshopYields = num || (hashSet.Contains("sighsorry.CreatureManager") && !cmDomainsEmpty);
			return new StepAsideDecision(num, workshopYields);
		}
	}
	public enum WorkshopAction
	{
		Copy,
		Wildblood,
		Ignore
	}
	public readonly struct WorkshopFile
	{
		public string Path { get; }

		public WorkshopAction Action { get; }

		public WorkshopFile(string path, WorkshopAction action)
		{
			Path = path;
			Action = action;
		}
	}
	public sealed class WorkshopPlan
	{
		public IReadOnlyList<WorkshopFile> Files { get; }

		public IReadOnlyList<string> SampleTextures { get; }

		internal WorkshopPlan(IReadOnlyList<WorkshopFile> files, IReadOnlyList<string> sampleTextures)
		{
			Files = files;
			SampleTextures = sampleTextures;
		}
	}
	public static class WorkshopImport
	{
		public const string SourceFolder = "BepInEx/config/CreatureManager";

		private static readonly Regex Domain = new Regex("^(factions\\.yml|(creatures|attacks|ai|projectile)(_[^/]*)?\\.ya?ml)$", RegexOptions.CultureInvariant);

		private static readonly Regex WildbloodFile = new Regex("^(levels|karma)(_[^/]*)?\\.yml$", RegexOptions.CultureInvariant);

		public static WorkshopPlan Plan(IEnumerable<string> relativePaths)
		{
			List<WorkshopFile> list = new List<WorkshopFile>();
			List<string> list2 = new List<string>();
			foreach (string item in relativePaths ?? Enumerable.Empty<string>())
			{
				string text = (item ?? "").Replace('\\', '/');
				WorkshopAction workshopAction = Classify(text);
				list.Add(new WorkshopFile(text, workshopAction));
				if (workshopAction == WorkshopAction.Copy && text.StartsWith("textures/", StringComparison.Ordinal) && text.Substring("textures/".Length).StartsWith("cm_", StringComparison.Ordinal))
				{
					list2.Add(text);
				}
			}
			return new WorkshopPlan(list, list2);
		}

		public static bool DomainsEmpty(IReadOnlyDictionary<string, string> domainFiles)
		{
			foreach (KeyValuePair<string, string> domainFile in domainFiles)
			{
				if (!Domain.IsMatch(domainFile.Key.Replace('\\', '/')))
				{
					continue;
				}
				string[] array = (domainFile.Value ?? "").Split('\n');
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length > 0 && text[0] != '#')
					{
						return false;
					}
				}
			}
			return true;
		}

		private static WorkshopAction Classify(string path)
		{
			if (path.Length == 0 || path[0] == '/' || path.Contains(":") || path.Split('/').Any((string part) => part == ".." || part == "."))
			{
				return WorkshopAction.Ignore;
			}
			if (Domain.IsMatch(path))
			{
				return WorkshopAction.Copy;
			}
			if (WildbloodFile.IsMatch(path))
			{
				return WorkshopAction.Wildblood;
			}
			int num = path.IndexOf('/');
			if (num > 0 && path.IndexOf('/', num + 1) < 0)
			{
				string text = path.Substring(0, num);
				string text2 = path.Substring(num + 1);
				if (text == "localization" && (text2.EndsWith(".yml", StringComparison.Ordinal) || text2.EndsWith(".yaml", StringComparison.Ordinal)) && text2.Length > 4)
				{
					return WorkshopAction.Copy;
				}
				if (text == "textures" && text2.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && text2.Length > 4)
				{
					return WorkshopAction.Copy;
				}
			}
			return WorkshopAction.Ignore;
		}
	}
}
namespace ExtraPotions.Bestiary.Config
{
	public static class BestiarySettings
	{
		public const string General = "1 - General";

		public const string Mimic = "2 - Crypt Mimic";

		public const string Slinger = "3 - Greydwarf Slinger";

		public const string RimeGiant = "4 - Rime Giant";

		public const string Kraken = "5 - Kraken";

		public const string Crossbowman = "6 - Skeleton Crossbowman";

		public const string BoneCrossbow = "7 - Bone Crossbow";

		public const string Arsenal = "8 - Skeleton Arsenal";

		public const string Executioner = "25 - Crypt Executioner";

		public const string Greataxe = "26 - Executioner Greataxe";

		public const string Ballista = "28 - Bone Ballista";

		public const string Workshop = "30 - Workshop";

		public const string BogHook = "41 - Bog Hook";

		public const string Javelineer = "42 - Fuling Javelineer";

		public const string SlagCharred = "43 - Slag Charred";

		public const string MarrowLantern = "44 - Marrow Lantern";

		private const SettingScope L = SettingScope.Local;

		private const SettingScope SA = SettingScope.ServerAuthoritative;

		private static readonly Dictionary<string, SettingSpec> ById = All.ToDictionary((SettingSpec s) => s.Id);

		public static IReadOnlyList<SettingSpec> All { get; } = Build();

		public static SettingSpec? Find(string id)
		{
			if (!ById.TryGetValue(id, out SettingSpec value))
			{
				return null;
			}
			return value;
		}

		public static SettingSpec ArsenalSwitch(string title)
		{
			return ById["8 - Skeleton Arsenal.Skeleton " + title];
		}

		private static SettingSpec S(string section, string key, object value, string description, double? min = null, double? max = null, SettingScope scope = SettingScope.ServerAuthoritative)
		{
			return new SettingSpec(section, key, scope, value, description, min, max);
		}

		private static SettingSpec[] Build()
		{
			return new SettingSpec[127]
			{
				S("1 - General", "Enabled", true, "Turns all of Bestiary on or off. Off: no Bestiary creature spawns and no recipe is offered; creatures and items already in the world stay."),
				S("1 - General", "Compendium Pages", true, "Shows the Compendium pages for Bestiary's creatures and items (where found, what it does, how to fight it).", null, null, SettingScope.Local),
				S("1 - General", "Workshop Enabled", true, "The creature workshop: clone and edit creatures, attacks, AI, factions, projectiles and textures from the workshop files."),
				S("2 - Crypt Mimic", "Enabled", true, "Crypt mimics. When a crypt first fills its chests, each listed chest may be a mimic instead: it looks exactly like the chest until someone opens it (it bites whoever does) or hits it. Off: no new mimics; ones already in the world stay."),
				S("2 - Crypt Mimic", "Chance", 15f, "Percent of the listed chests that are mimics. Only crypts generated after this is on get them.", 0.0, 100.0),
				S("2 - Crypt Mimic", "Chests", "TreasureChest_forestcrypt, TreasureChest_sunkencrypt", "The chest prefabs that may be mimics, separated by commas. Hildir's crypt chest (her quest key) and TreasureChest_fCrypt (it also stands in fortresses and troll caves) are left out on purpose."),
				S("2 - Crypt Mimic", "Bite Cooldown", 3f, "Seconds after a lunge before it can bite again; meanwhile it hops after you.", 0.5, 60.0),
				S("2 - Crypt Mimic", "Bite Damage", 20f, "Slash damage of its bite (the lunge and the ambush), before stars (the crypt skeleton's sword: 25).", 0.0, 1000.0),
				S("2 - Crypt Mimic", "Run Speed", 5f, "How fast it bounds after you (the crypt skeleton runs at 4); it wanders at half this.", 0.5, 10.0),
				S("3 - Greydwarf Slinger", "Enabled", true, "Greydwarf slingers: a greydwarf with a slingshot that stands and shoots stones like a skeleton archer, at any range, and never melees. Off: no new slingers; ones already in the world stay."),
				S("3 - Greydwarf Slinger", "Share", 10f, "Percent of the greydwarfs spawning in the biomes below that come as slingers instead.", 0.0, 100.0),
				S("3 - Greydwarf Slinger", "Nests", true, "Greydwarf nests spawn them too, not only the wild."),
				S("3 - Greydwarf Slinger", "Biomes", BiomeMask.BlackForest, "Where greydwarfs can come as slingers. Several biomes are separated by commas, like BlackForest, Meadows."),
				S("3 - Greydwarf Slinger", "Shot Interval", 3.5f, "Seconds between its shots at the least.", 0.5, 60.0),
				S("3 - Greydwarf Slinger", "Stone Damage", 12f, "Blunt damage of a stone, before stars (a greydwarf's thrown rock: 10).", 0.0, 500.0),
				S("3 - Greydwarf Slinger", "Stone Speed", 16f, "Meters a second the stone flies (a greydwarf's thrown rock: 12); it arcs onto its target.", 5.0, 60.0),
				S("3 - Greydwarf Slinger", "Range", 22f, "Distance in meters at which a Greydwarf Slinger starts throwing stones at a target.", 6.0, 60.0),
				S("4 - Rime Giant", "Enabled", true, "Rime giants: a very rare giant of the Mountains, a troll crusted in plates of ice that sleeps by day like a snowy outcrop. At most one per mountain, ever. Off: no new giants; ones already in the world stay."),
				S("4 - Rime Giant", "Mountains", 60f, "Percent of mountains that hold a giant, fixed by the world seed. Snowy hilltops under about 150 m across don't count. Once a mountain's giant has come it never gets another, even after it dies.", 0.0, 100.0),
				S("4 - Rime Giant", "Chance", 25f, "Percent per zone roll, while someone roams a mountain whose giant has not come yet.", 0.0, 100.0),
				S("4 - Rime Giant", "Interval", 3600f, "Seconds between a zone's rolls.", 60.0, 86400.0),
				S("4 - Rime Giant", "Biomes", BiomeMask.Mountain, "The biomes whose regions count as mountains. Several are separated by commas."),
				S("4 - Rime Giant", "Health", 1500f, "Its health before stars (a Troll: 600).", 1.0, 100000.0),
				S("4 - Rime Giant", "Plates", 8, "Plates of ice it wears, 0 to 8.", 0.0, 8.0),
				S("4 - Rime Giant", "Armored Damage", 15f, "Percent of a physical hit that gets through with every plate on; each plate lost lets an equal part more through.", 0.0, 100.0),
				S("4 - Rime Giant", "Shatter At", 40f, "Percent of its health at which the plates left all break off.", 0.0, 100.0),
				S("4 - Rime Giant", "Fire Per Plate", 30f, "Fire damage that breaks one plate (a fire arrow against its weakness: about 33).", 1.0, 1000.0),
				S("4 - Rime Giant", "Regrow Delay", 12f, "Seconds unhurt, out of the fight, before the plates grow back.", 0.0, 600.0),
				S("4 - Rime Giant", "Regrow Interval", 5f, "Seconds per plate as they grow back.", 0.5, 600.0),
				S("4 - Rime Giant", "Sweep Damage", 75f, "Blunt damage of its fist, with heavy knockback (a troll's punch: 60).", 0.0, 1000.0),
				S("4 - Rime Giant", "Slam Damage", 60f, "Blunt damage where its fists land in the slam.", 0.0, 1000.0),
				S("4 - Rime Giant", "Avalanche Damage", 45f, "Blunt damage, plus half as much frost, where the slam's wave rolls over you.", 0.0, 1000.0),
				S("4 - Rime Giant", "Avalanche Length", 24f, "Meters the wave rolls on flat ground; far further downhill, a few meters uphill. 0 turns the avalanche off.", 0.0, 60.0),
				S("4 - Rime Giant", "Boulder Damage", 40f, "Frost damage of its ice boulder in the burst where it lands, plus half as much blunt.", 0.0, 1000.0),
				S("5 - Kraken", "Enabled", true, "Krakens: a boss of the deep ocean that hunts ships. Outrun it under sail, or it grabs the ship from below and attacks with six tentacles and then its head. Off: no new krakens; one already in the world stays."),
				S("5 - Kraken", "Chance", 0.3f, "Percent per zone roll while a ship with a crew crosses deep ocean. At 0.3 a crew sailing the open sea meets one about every hour or two.", 0.0, 100.0),
				S("5 - Kraken", "Interval", 3600f, "Seconds between a zone's rolls.", 60.0, 86400.0),
				S("5 - Kraken", "Min Depth", 25f, "Meters of water it needs: it only spawns over water this deep and never follows a ship into shallower water.", 6.0, 200.0),
				S("5 - Kraken", "Health", 3000f, "Its health (Bonemass: 5000, a serpent: 400). The game adds 30% for every other player within 100 m, as for any creature.", 1.0, 1000000.0),
				S("5 - Kraken", "Swim Speed", 3.5f, "Meters a second as it chases. Rowing (about 2) never escapes it; a longship or karve under sail with a fair wind does.", 0.5, 20.0),
				S("5 - Kraken", "Hunt Range", 90f, "Meters at which it notices a ship. It gives up once the ship is further than this.", 20.0, 300.0),
				S("5 - Kraken", "Attack Gap Min", 2.5f, "Seconds from one attack to the next at the least. It attacks one thing at a time: a slam, a bite, ink or a smash.", 0.5, 30.0),
				S("5 - Kraken", "Attack Gap Max", 4f, "Seconds from one attack to the next at the most; each gap is a random time between the two.", 0.5, 30.0),
				S("5 - Kraken", "Gap Factor Solo", 1.1f, "The gap between attacks is this many times longer with one player aboard (three or more: the gap as set).", 0.5, 5.0),
				S("5 - Kraken", "Gap Factor Pair", 1.2f, "The gap between attacks is this many times longer with two players aboard.", 0.5, 5.0),
				S("5 - Kraken", "Tentacle Gap Factor", 0.7f, "Under the ship the slams come quicker: the gap between them is this many times the attack gap.", 0.2, 3.0),
				S("5 - Kraken", "Phase Attacks", 6, "Attacks in each phase: this many tentacle slams under the ship, then this many from the head beside it, and round again.", 1.0, 30.0),
				S("5 - Kraken", "Slam Damage", 60f, "Blunt damage of a tentacle slamming across the deck.", 0.0, 1000.0),
				S("5 - Kraken", "Ship Damage", 40f, "Damage to the ship each time a tentacle smashes it in the head phase (a longship has 1000 health, a karve 500).", 0.0, 10000.0),
				S("5 - Kraken", "Ship Hit Interval", 10f, "Seconds between tentacle smashes on the ship while the head is up; a smash takes the next attack's turn.", 2.0, 120.0),
				S("5 - Kraken", "Bite Damage", 80f, "Pierce damage of its beak, at players near the rail.", 0.0, 1000.0),
				S("5 - Kraken", "Ink Damage", 15f, "Blunt damage of the ink it spits.", 0.0, 1000.0),
				S("5 - Kraken", "Ink Blind", 1.5f, "Seconds the ink covers the screen of a player it hits (dodge or block it and it doesn't), before it fades.", 0.0, 10.0),
				S("5 - Kraken", "Ink Interval", 6f, "Seconds between its ink spits at the least.", 1.0, 60.0),
				S("5 - Kraken", "Shield Parry Damage", 60f, "Pierce damage a creature takes when you parry its blow with the Kraken shield (made from the kraken's beak), at quality 1. Players are never bitten.", 0.0, 10000.0),
				S("5 - Kraken", "Shield Parry Damage Per Level", 10f, "Added to the parry damage for each quality level of the shield above the first.", 0.0, 1000.0),
				S("6 - Skeleton Crossbowman", "Damage Factor", 1f, "A bolt's damage, as blunt, against the bow of the skeleton archer it replaces (20), before stars. 1: the same as that archer.", 0.0, 10.0),
				S("6 - Skeleton Crossbowman", "Shot Interval", 6f, "Seconds between its shots at the least (the skeleton archer's: 4). The whole shot, spanning and loading included, takes about 5.5 of them.", 3.0, 60.0),
				S("6 - Skeleton Crossbowman", "Bolt Speed", 40f, "Meters a second the bolt flies, straight (the skeleton archer's arrow: 30).", 10.0, 120.0),
				S("6 - Skeleton Crossbowman", "Range", 25f, "Meters it shoots from (the skeleton archer: 20).", 6.0, 60.0),
				S("7 - Bone Crossbow", "Craftable", true, "The Bone Crossbow, the crossbowmen's crossbow of bones, can be made at the workbench."),
				S("7 - Bone Crossbow", "Recipe", "ECP_Spine:1, Wood:10:5, BoneFragments:12:6, LeatherScraps:4:2", "What it costs, as item:amount:amount per upgrade, separated by commas. Item names are the game's prefab names, like BoneFragments, Wood, DeerHide, TrophySkeleton; the spine is ECP_Spine."),
				S("7 - Bone Crossbow", "Workbench Level", 2, "The workbench level it needs (its Blunted Bone Bolts too).", 1.0, 5.0),
				S("7 - Bone Crossbow", "Damage", 30f, "Blunt damage of its own blow, before the bolt's (a Blunted Bone Bolt adds 27 blunt, the game's bone bolt 32 pierce; the Arbalest: 200 pierce).", 0.0, 500.0),
				S("7 - Bone Crossbow", "Damage Per Level", 4f, "Blunt damage added by each upgrade.", 0.0, 100.0),
				S("7 - Bone Crossbow", "Reload Time", 2.3f, "Seconds to span and load it with no Crossbows skill; the skill halves it (the Arbalest: 3.5).", 0.5, 20.0),
				S("7 - Bone Crossbow", "Bolt Recipe", "BoneFragments:8", "What a batch of Blunted Bone Bolts costs, as item:amount, separated by commas; made at the crossbow's workbench level."),
				S("7 - Bone Crossbow", "Bolts Per Craft", 20, "Blunted Bone Bolts made at once.", 1.0, 100.0),
				S("7 - Bone Crossbow", "Bolt Damage", 27f, "Blunt damage a Blunted Bone Bolt adds to the crossbow's own: a flint arrow's, as blunt (flint arrows 27 pierce; the game's bone bolt 32 pierce).", 0.0, 500.0),
				S("8 - Skeleton Arsenal", "Enabled", true, "All the arsenal skeletons, the crossbowman too, the master switch. Off: none of them spawns, whatever their own switches below say; ones already in the world stay."),
				ArsenalRow("Cutthroat", "bone dagger"),
				ArsenalRow("Swordsman", "bone sword"),
				ArsenalRow("Axeman", "bone axe"),
				ArsenalRow("Bonebreaker", "bone mace"),
				ArsenalRow("Spearman", "bone spear"),
				ArsenalRow("Halberdier", "bone atgeir"),
				ArsenalRow("Bowman", "bone bow"),
				S("8 - Skeleton Arsenal", "Skeleton Crossbowman", true, "The Skeleton Crossbowman (bone crossbow, blunt bone bolts; its shot's numbers are in section 6) spawns in place of some of the game's skeletons that could be archers, wherever and whenever they spawn. Off: no new ones; ones already in the world stay."),
				S("25 - Crypt Executioner", "Enabled", true, "The Crypt Executioner: a skeleton headsman with a bone greataxe, a mini boss waiting in some of the Black Forest's burial chambers. Off: no chamber gets one, and the ones already placed raise none."),
				S("25 - Crypt Executioner", "Chambers", 25f, "Percent of burial chambers that hold one, decided once per chamber: when it is generated, or for chambers from before the mod when first loaded.", 0.0, 100.0),
				S("25 - Crypt Executioner", "Respawn Days", 3f, "Game days before it comes back to its chamber after it died.", 0.1, 100.0),
				S("25 - Crypt Executioner", "Health", 900f, "Its health before stars (a troll: 600).", 1.0, 100000.0),
				S("25 - Crypt Executioner", "Axehead Chance", 50f, "Percent chance it drops its axehead, from which the Executioner's Greataxe is made.", 0.0, 100.0),
				S("25 - Crypt Executioner", "Slam Damage", 60f, "Slash damage of the overhead slam, where the axe lands.", 0.0, 1000.0),
				S("25 - Crypt Executioner", "Sweep Damage", 35f, "Slash damage of the low sweep across its front, where the axe head passes.", 0.0, 1000.0),
				S("25 - Crypt Executioner", "Spin Damage", 50f, "Slash damage of the spin, where its axe head passes.", 0.0, 1000.0),
				S("25 - Crypt Executioner", "Throw Damage", 55f, "Slash damage of the thrown axe.", 0.0, 1000.0),
				S("25 - Crypt Executioner", "Rear Strike Damage", 55f, "Slash damage of the strike behind it, used when two or more foes are near and one is behind.", 0.0, 1000.0),
				S("25 - Crypt Executioner", "Axe Head Radius", 0.6f, "Meters round the blade's edge that hit in the slam (where the axe lands) and the spin (the ring the head draws; inside it is safe).", 0.1, 2.0),
				S("25 - Crypt Executioner", "Slam Windup Speed", 1.35f, "How much faster than its animation the slam is raised and cocked before it comes down (1 = as animated).", 0.5, 3.0),
				S("25 - Crypt Executioner", "Summons", 2, "Skeletons it may have raised at once from its thrown axe's bones; 0 raises none.", 0.0, 10.0),
				S("26 - Executioner Greataxe", "Recipe", true, "Whether the greataxe can be made at the workbench."),
				S("26 - Executioner Greataxe", "Slash", 55f, "Slash damage (a fully upgraded Bronze Axe: 55).", 0.0, 1000.0),
				S("26 - Executioner Greataxe", "Chop", 49f, "Chop damage, for trees (a fully upgraded Bronze Axe: 49).", 0.0, 1000.0),
				S("26 - Executioner Greataxe", "Spines", 1, "Spines (the bone weapons' drop from skeletons) the recipe takes besides the axehead; without the skeleton arsenal, bone fragments take their place.", 0.0, 100.0),
				S("26 - Executioner Greataxe", "Bone Fragments", 10, "Bone fragments the recipe takes.", 0.0, 100.0),
				S("26 - Executioner Greataxe", "Workbench Level", 3, "Workbench level required to craft the Executioner Greataxe.", 1.0, 5.0),
				S("28 - Bone Ballista", "Enabled", true, "A small ballista of bones in the hammer's Misc. tab beside the game's ballista (3 spines, 25 bone fragments), and its Bone Missiles at the workbench (level 2: 5 bone fragments and 2 feathers make 20). Hold it with the use key, aim with the mouse (45 degrees either way), shoot with attack; it reloads from the missiles you carry. Off: neither can be made; ballistas already built stay and still shoot."),
				S("30 - Workshop", "Data Folder", "BepInEx/config/Meadworks/bestiary/workshop", "Folder of the workshop files (creatures, attacks, AI, factions, projectiles, localization, textures), relative to the game folder and inside BepInEx/config."),
				S("30 - Workshop", "Hot Reload", true, "Workshop files reload when saved; creatures already loaded keep what they have completed."),
				S("30 - Workshop", "Texture Sync", true, "The server sends its workshop textures to clients, which keep them in a cache by content hash."),
				S("30 - Workshop", "Texture Cache Limit", 256, "Megabytes of synced workshop textures this client keeps.", 16.0, 4096.0, SettingScope.Local),
				S("41 - Bog Hook", "Enabled", true, "Draugr Hooks on the Swamp surface: a Draugr that throws a hook, pulls, then closes in. Health and drops stay the Draugr's. Off: no new ones; ones already in the world stay."),
				S("41 - Bog Hook", "Share", 8f, "Percent of wild Draugr in the biomes below that come as Draugr Hooks. Draugr Elites, creature spawners and draugr piles stay Draugr.", 0.0, 100.0),
				S("41 - Bog Hook", "Biomes", BiomeMask.Swamp, "Where wild Draugr can come as Draugr Hooks. Several biomes are separated by commas."),
				S("41 - Bog Hook", "Hook Damage", 18f, "Pierce of the hook, before stars. The Draugr's own sword stays its melee.", 0.0, 500.0),
				S("41 - Bog Hook", "Range", 14f, "Meters at which it can throw the hook.", 4.0, 40.0),
				S("41 - Bog Hook", "Pull", 6f, "Meters the hook drags the target closer, and it stops if the ground is in the way.", 0.0, 20.0),
				S("41 - Bog Hook", "Interval", 8f, "Seconds before it can throw the hook again.", 1.0, 60.0),
				S("42 - Fuling Javelineer", "Enabled", true, "Fuling Spearmen on the Plains: an ordinary Fuling that throws one spear, then uses the Fuling's own melee. Health and drops stay the Fuling's. Not a Fuling berserker, not a Fuling shaman, and not a village spawner. Off: no new ones; ones already in the world stay."),
				S("42 - Fuling Javelineer", "Share", 8f, "Percent of wild Fuling in the biomes below that come as Fuling Spearmen. Berserkers, shamans and village spawners stay as they are.", 0.0, 100.0),
				S("42 - Fuling Javelineer", "Biomes", BiomeMask.Plains, "Where wild Fuling can come as Fuling Spearmen. Several biomes are separated by commas."),
				S("42 - Fuling Javelineer", "Javelin Damage", 22f, "Pierce of the one spear, before stars. After that throw it uses the Fuling's own melee.", 0.0, 500.0),
				S("42 - Fuling Javelineer", "Throw Range", 16f, "Meters at which it throws that spear, once.", 4.0, 40.0),
				S("43 - Slag Charred", "Enabled", true, "Slag Charred in the Ashlands: a Charred warrior wearing three plates. Fire breaks a plate. Physical blows still land while plates remain, so it can be killed with no fire. At most one per Ashlands region, ever, and never within 1000 m of another. No boss bar. Killing it does not set a world key. Off: no new ones; ones already in the world stay."),
				S("43 - Slag Charred", "Regions", 40f, "Percent of Ashlands regions that hold one, fixed by the world seed. A region needs at least 40 edge cells (about 150 m across), the same size rule as a Rime Giant's mountain. Once its Charred has come, the region never gets another until Delve clears the claim.", 0.0, 100.0),
				S("43 - Slag Charred", "Chance", 10f, "Percent per zone roll, while someone is in an Ashlands region whose Charred has not come yet.", 0.0, 100.0),
				S("43 - Slag Charred", "Interval", 3600f, "Seconds between a zone's rolls.", 60.0, 86400.0),
				S("43 - Slag Charred", "Biomes", BiomeMask.AshLands, "The biomes whose regions can hold one. Several are separated by commas."),
				S("43 - Slag Charred", "Health", 700f, "Its health before stars (a Charred warrior: 600, an Asksvin: 800).", 1.0, 100000.0),
				S("43 - Slag Charred", "Plates", 3, "Plates it wears. Fire breaks them one at a time.", 0.0, 3.0),
				S("43 - Slag Charred", "Armored Damage", 40f, "Percent of a physical hit that gets through with every plate on. Each plate lost lets an equal part more through. With none left, the whole hit lands.", 1.0, 100.0),
				S("43 - Slag Charred", "Fire Per Plate", 40f, "Fire on a hit that breaks one plate. With no fire, physical damage still gets through, so the Charred can be killed anyway.", 1.0, 1000.0),
				S("44 - Marrow Lantern", "Enabled", true, "Marrow Lanterns in the Deep North: a Deep North skeleton carrying a lantern. Some Deep North skeleton spawners can roll one, and those spawners still roll the ordinary skeleton. It is not a wild spawn, not a ShadowPerson, and not part of the bone arsenal. It drops one freeze gland (a Drake drops one) and coins. Off: no new ones; ones already in the world stay."),
				S("44 - Marrow Lantern", "Places", 25f, "Percent of Deep North skeleton spawners that can roll a Marrow Lantern, fixed by the world seed and the spawner's place.", 0.0, 100.0),
				S("44 - Marrow Lantern", "Chance", 40f, "Percent of spawns at those spawners that are Marrow Lanterns. The rest stay Deep North skeletons.", 0.0, 100.0),
				S("44 - Marrow Lantern", "Reveal", 12, "Meters at which this skeleton's own cloak drops, when Wildblood gives it Cloaked (a Rime Giant's reveal is 15). It does not reveal creatures around it. Wildblood owns stars. Bestiary spawns it at level 1. If Wildblood is absent, it stays level 1 and has no cloak.", 0.0, 60.0)
			};
		}

		private static SettingSpec ArsenalRow(string title, string weapon)
		{
			return S("8 - Skeleton Arsenal", "Skeleton " + title, true, "The Skeleton " + title + " (" + weapon + ") spawns in place of some of the game's skeletons, wherever and whenever they spawn. Off: no new ones; ones already in the world stay.");
		}
	}
	[Flags]
	public enum BiomeMask
	{
		None = 0,
		Meadows = 1,
		Swamp = 2,
		Mountain = 4,
		BlackForest = 8,
		Plains = 0x10,
		AshLands = 0x20,
		DeepNorth = 0x40,
		Ocean = 0x100,
		Mistlands = 0x200,
		Land = 0x27F,
		All = 0x37F
	}
	public static class BiomeMasks
	{
		private static readonly BiomeMask[] Singles = new BiomeMask[9]
		{
			BiomeMask.Meadows,
			BiomeMask.Swamp,
			BiomeMask.Mountain,
			BiomeMask.BlackForest,
			BiomeMask.Plains,
			BiomeMask.AshLands,
			BiomeMask.DeepNorth,
			BiomeMask.Ocean,
			BiomeMask.Mistlands
		};

		public static BiomeMask? Parse(string? text)
		{
			BiomeMask biomeMask = BiomeMask.None;
			string[] array = (text ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					if (!Enum.TryParse<BiomeMask>(text2, ignoreCase: true, out var result) || !Enum.IsDefined(typeof(BiomeMask), result))
					{
						return null;
					}
					biomeMask |= result;
				}
			}
			return biomeMask;
		}

		public static string Format(BiomeMask mask)
		{
			if (Enum.IsDefined(typeof(BiomeMask), mask))
			{
				return mask.ToString();
			}
			return string.Join(", ", from b in Singles
				where (mask & b) == b
				select b.ToString());
		}

		public static bool Contains(BiomeMask mask, BiomeMask biome)
		{
			if (biome != BiomeMask.None)
			{
				return (mask & biome) == biome;
			}
			return false;
		}

		public static IReadOnlyList<BiomeMask> Each(BiomeMask mask)
		{
			return Singles.Where((BiomeMask b) => (mask & b) == b).ToArray();
		}
	}
	public enum SettingScope
	{
		Local,
		ServerAuthoritative,
		Cheat
	}
	public sealed class SettingSpec
	{
		public string Section { get; }

		public string Key { get; }

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

		public SettingScope Scope { get; }

		public object Default { get; }

		public Type ValueType => Default.GetType();

		public double? Minimum { get; }

		public double? Maximum { get; }

		public string Description { get; }

		public SettingSpec(string section, string key, SettingScope scope, object defaultValue, string description, double? minimum = null, double? maximum = null)
		{
			Section = section;
			Key = key;
			Scope = scope;
			Default = defaultValue ?? throw new ArgumentNullException("defaultValue");
			Description = description;
			Minimum = minimum;
			Maximum = maximum;
		}

		public T Clamp<T>(T value)
		{
			if (value is float)
			{
				object obj = value;
				float num = (float)((obj is float) ? obj : null);
				if (float.IsNaN(num) || float.IsInfinity(num))
				{
					return (T)Default;
				}
				return (T)(object)(float)Limit(num);
			}
			if (value is int)
			{
				object obj2 = value;
				int num2 = (int)((obj2 is int) ? obj2 : null);
				return (T)(object)(int)Limit(num2);
			}
			return value;
		}

		private double Limit(double value)
		{
			if (Minimum.HasValue && value < Minimum.Value)
			{
				value = Minimum.Value;
			}
			if (Maximum.HasValue && value > Maximum.Value)
			{
				value = Maximum.Value;
			}
			return value;
		}
	}
}
namespace ExtraPotions.Bestiary.Catalog
{
	public sealed class CompendiumPage
	{
		public string Id { get; }

		public string TitleKey { get; }

		public string BodyKey { get; }

		public IReadOnlyList<string> Prefabs { get; }

		public bool Items { get; }

		internal CompendiumPage(string id, string titleKey, string bodyKey, IEnumerable<string> prefabs, bool items)
		{
			Id = id;
			TitleKey = titleKey;
			BodyKey = bodyKey;
			Prefabs = prefabs.ToArray();
			Items = items;
		}
	}
	public static class CompendiumCatalog
	{
		public static IReadOnlyList<CompendiumPage> Pages { get; } = Build();

		public static IReadOnlyList<string> Keys { get; } = (from k in Pages.SelectMany((CompendiumPage p) => new string[2] { p.TitleKey, p.BodyKey })
			where k.StartsWith("compendium_", StringComparison.Ordinal)
			select k).Concat(new string[1] { "compendium_items" }).Distinct().ToArray();

		private static IReadOnlyList<CompendiumPage> Build()
		{
			List<CompendiumPage> list = CreatureCatalog.All.Select((CreatureDefinition c) => new CompendiumPage(c.Prefab, c.Word ?? "compendium_raised_title", "compendium_" + Body(c) + "_body", new string[1] { c.Prefab }, items: false)).ToList();
			list.Add(new CompendiumPage("bone-arsenal", "compendium_arsenal_title", "compendium_arsenal_body", ItemCatalog.BoneWeapons.Select((ItemDefinition i) => i.Prefab).Concat(new string[2] { "ECP_Spine", "ECP_ArrowBone" }), items: true));
			list.Add(new CompendiumPage("bone-crossbow", "item_ecp_bonecrossbow", "compendium_crossbow_body", new string[2] { "ECP_BoneCrossbow", "ECP_BoltBoneBlunt" }, items: true));
			list.Add(new CompendiumPage("executioner-gear", "item_ecp_executionergreataxe", "compendium_greataxe_body", new string[2] { "ECP_ExecutionerGreataxe", "ECP_ExecutionerAxehead" }, items: true));
			list.Add(new CompendiumPage("kraken-gear", "compendium_kraken_gear_title", "compendium_kraken_gear_body", new string[4] { "ECP_KrakenBeak", "ECP_KrakenMeat", "ECP_KrakenMeatCooked", "ECP_ShieldKraken" }, items: true));
			return list;
		}

		private static string Body(CreatureDefinition c)
		{
			if (c.Spawn == SpawnKind.SkeletonDraw && c.Prefab != "ECP_SkeletonCrossbowman")
			{
				return "arsenal_creature";
			}
			return (c.Prefab.StartsWith("MW_", StringComparison.Ordinal) ? c.Prefab.Substring(3) : c.Prefab.Substring(4)).ToLowerInvariant();
		}
	}
	public enum BestiaryModule
	{
		Crypt,
		Forest,
		Mountain,
		Sea,
		Skeletons,
		ArsenalCrafting,
		Workshop,
		Kit
	}
	public enum SpawnKind
	{
		MimicSwap,
		GreydwarfShare,
		SkeletonDraw,
		ZoneEntry,
		ChamberSpawner,
		Raised,
		WildShare,
		RegionClaim,
		SpawnerRoll
	}
	public sealed class DropSpec
	{
		public string Item { get; }

		public int Min { get; }

		public int Max { get; }

		public float Chance { get; }

		public bool LevelMultiplier { get; }

		public bool DontScale { get; }

		public string? ChanceSetting { get; }

		public DropSpec(string item, int min, int max, float chance = 1f, bool levelMultiplier = true, bool dontScale = false, string? chanceSetting = null)
		{
			Item = item;
			Min = min;
			Max = max;
			Chance = chance;
			LevelMultiplier = levelMultiplier;
			DontScale = dontScale;
			ChanceSetting = chanceSetting;
		}
	}
	public sealed class CreatureDefinition
	{
		public string Prefab { get; }

		public string? Word { get; }

		public string BasePrefab { get; }

		public BestiaryModule Module { get; }

		public SpawnKind Spawn { get; }

		public IReadOnlyList<DropSpec> Drops { get; }

		public bool KeepBaseDrops { get; }

		public float Scale { get; }

		public string? HealthSetting { get; }

		public bool Boss { get; }

		public bool DropsChestLoot { get; }

		public bool Worthless { get; }

		public CreatureDefinition(string prefab, string? word, string basePrefab, BestiaryModule module, SpawnKind spawn, IReadOnlyList<DropSpec> drops, bool keepBaseDrops, float scale = 1f, string? healthSetting = null, bool boss = false, bool dropsChestLoot = false, bool worthless = false)
		{
			Prefab = prefab;
			Word = word;
			BasePrefab = basePrefab;
			Module = module;
			Spawn = spawn;
			Drops = drops;
			KeepBaseDrops = keepBaseDrops;
			Scale = scale;
			HealthSetting = healthSetting;
			Boss = boss;
			DropsChestLoot = dropsChestLoot;
			Worthless = worthless;
		}
	}
	public static class CreatureCatalog
	{
		private const string Skeleton = "Skeleton";

		public static readonly IReadOnlyList<(string Title, string Weapon)> ArsenalTitles = new(string, string)[7]
		{
			("Cutthroat", "Dagger"),
			("Swordsman", "Sword"),
			("Axeman", "Axe"),
			("Bonebreaker", "Mace"),
			("Spearman", "Spear"),
			("Halberdier", "Atgeir"),
			("Bowman", "Bow")
		};

		public static IReadOnlyList<CreatureDefinition> All { get; } = Build();

		public static IReadOnlyList<CreatureDefinition> ArsenalSkeletons { get; } = All.Where((CreatureDefinition c) => ArsenalTitles.Any<(string, string)>(((string Title, string Weapon) t) => c.Prefab == "ECP_Skeleton" + t.Title)).ToArray();

		private static DropSpec Spine()
		{
			return new DropSpec("ECP_Spine", 1, 1, 0.1f, levelMultiplier: false);
		}

		public static CreatureDefinition? Find(string prefab)
		{
			return All.FirstOrDefault((CreatureDefinition c) => c.Prefab == prefab);
		}

		private static CreatureDefinition[] Build()
		{
			List<CreatureDefinition> list = new List<CreatureDefinition>();
			list.Add(new CreatureDefinition("ECP_CryptMimic", "enemy_ecp_cryptmimic", "Skeleton", BestiaryModule.Crypt, SpawnKind.MimicSwap, Array.Empty<DropSpec>(), keepBaseDrops: false, 1f, null, boss: false, dropsChestLoot: true));
			list.Add(new CreatureDefinition("ECP_GreydwarfSlinger", "enemy_ecp_greydwarfslinger", "Greydwarf", BestiaryModule.Forest, SpawnKind.GreydwarfShare, new DropSpec[1]
			{
				new DropSpec("Stone", 2, 4)
			}, keepBaseDrops: true));
			list.Add(new CreatureDefinition("ECP_RimeGiant", "enemy_ecp_rimegiant", "Troll", BestiaryModule.Mountain, SpawnKind.ZoneEntry, new DropSpec[3]
			{
				new DropSpec("Crystal", 4, 8),
				new DropSpec("FreezeGland", 3, 6),
				new DropSpec("SilverOre", 3, 6)
			}, keepBaseDrops: false, 1.4f, "4 - Rime Giant.Health"));
			list.Add(new CreatureDefinition("ECP_Kraken", "enemy_ecp_kraken", "Serpent", BestiaryModule.Sea, SpawnKind.ZoneEntry, new DropSpec[4]
			{
				new DropSpec("Chitin", 20, 30),
				new DropSpec("ECP_KrakenMeat", 5, 8),
				new DropSpec("Coins", 150, 300),
				new DropSpec("ECP_KrakenBeak", 1, 2, 1f, levelMultiplier: false, dontScale: true)
			}, keepBaseDrops: false, 1f, "5 - Kraken.Health", boss: true));
			List<CreatureDefinition> list2 = list;
			foreach (var arsenalTitle in ArsenalTitles)
			{
				string item = arsenalTitle.Title;
				list2.Add(new CreatureDefinition("ECP_Skeleton" + item, "enemy_ecp_skeleton" + item.ToLowerInvariant(), "Skeleton", BestiaryModule.Skeletons, SpawnKind.SkeletonDraw, new DropSpec[1] { Spine() }, keepBaseDrops: true));
			}
			list2.Add(new CreatureDefinition("ECP_SkeletonCrossbowman", "enemy_ecp_skeletoncrossbowman", "Skeleton", BestiaryModule.Skeletons, SpawnKind.SkeletonDraw, new DropSpec[2]
			{
				Spine(),
				new DropSpec("ECP_BoltBoneBlunt", 1, 3, 0.5f)
			}, keepBaseDrops: true));
			list2.Add(new CreatureDefinition("ECP_Headsman", "enemy_ecp_headsman", "Skeleton", BestiaryModule.Crypt, SpawnKind.ChamberSpawner, new DropSpec[3]
			{
				new DropSpec("Coins", 20, 40, 1f, levelMultiplier: false),
				new DropSpec("BoneFragments", 4, 8, 1f, levelMultiplier: false),
				new DropSpec("ECP_ExecutionerAxehead", 1, 1, 0.5f, levelMultiplier: false, dontScale: false, "25 - Crypt Executioner.Axehead Chance")
			}, keepBaseDrops: false, 1.25f, "25 - Crypt Executioner.Health"));
			list2.Add(new CreatureDefinition("ECP_HeadsmanSkeleton", null, "Skeleton_NoArcher", BestiaryModule.Crypt, SpawnKind.Raised, Array.Empty<DropSpec>(), keepBaseDrops: false, 1f, null, boss: false, dropsChestLoot: false, worthless: true));
			list2.Add(new CreatureDefinition("MW_BogHook", "enemy_mw_boghook", "Draugr", BestiaryModule.Kit, SpawnKind.WildShare, Array.Empty<DropSpec>(), keepBaseDrops: true));
			list2.Add(new CreatureDefinition("MW_FulingJavelineer", "enemy_mw_fulingjavelineer", "Goblin", BestiaryModule.Kit, SpawnKind.WildShare, Array.Empty<DropSpec>(), keepBaseDrops: true));
			list2.Add(new CreatureDefinition("MW_SlagCharred", "enemy_mw_slagcharred", "Charred_Melee", BestiaryModule.Kit, SpawnKind.RegionClaim, Array.Empty<DropSpec>(), keepBaseDrops: true, 1f, "43 - Slag Charred.Health"));
			list2.Add(new CreatureDefinition("MW_MarrowLantern", "enemy_mw_marrowlantern", "Skeleton_DeepNorth", BestiaryModule.Kit, SpawnKind.SpawnerRoll, new DropSpec[2]
			{
				new DropSpec("FreezeGland", 1, 1, 1f, levelMultiplier: false, dontScale: true),
				new DropSpec("Coins", 8, 20)
			}, keepBaseDrops: false));
			return list2.ToArray();
		}
	}
	public static class ExecutionerReach
	{
		public const float MeleeAiRange = 1.7f;

		public const float DefaultAxeRadius = 0.6f;

		public const float RestInside = 0.1f;

		public const float PlayerRadius = 0.5f;

		public const float SlamAhead = 1.25f;

		public const float SweepAhead = 1f;

		public const float SpinRing = 1.4f;

		public const float BodyRadius = 0.5f;

		public static float RestDistance(float aiRange)
		{
			return aiRange - 0.1f;
		}

		public static float HitReach(float axeRadius, float targetRadius)
		{
			return axeRadius + targetRadius;
		}

		public static float SlamMiss(float aiRange)
		{
			return Math.Abs(RestDistance(aiRange) - 1.25f);
		}

		public static float SweepMiss(float aiRange)
		{
			return Math.Abs(RestDistance(aiRange) - 1f);
		}

		public static float SpinMiss(float aiRange)
		{
			return Math.Abs(RestDistance(aiRange) - 1.4f);
		}

		public static float SlamFarthest(float axeRadius, float targetRadius)
		{
			return 1.25f + HitReach(axeRadius, targetRadius);
		}

		public static bool HitsEveryMelee(float aiRange, float axeRadius, float targetRadius)
		{
			float num = HitReach(axeRadius, targetRadius);
			if (SlamMiss(aiRange) <= num && SweepMiss(aiRange) <= num)
			{
				return SpinMiss(aiRange) <= num;
			}
			return false;
		}
	}
	public enum ItemKind
	{
		Weapon,
		Ammo,
		Material,
		Food,
		Shield
	}
	public static class Stations
	{
		public const string Workbench = "piece_workbench";

		public const string Forge = "forge";
	}
	public readonly struct FoodStats
	{
		public float Health { get; }

		public float Stamina { get; }

		public float Regen { get; }

		public float BurnTime { get; }

		public FoodStats(float health, float stamina, float regen, float burnTime)
		{
			Health = health;
			Stamina = stamina;
			Regen = regen;
			BurnTime = burnTime;
		}

		public override string ToString()
		{
			return $"{Health}/{Stamina}/{Regen}/{BurnTime}s";
		}
	}
	public sealed class RecipeSpec
	{
		public string Station { get; }

		public int Level { get; }

		public int Amount { get; }

		public string? Cost { get; }

		public string? CostSetting { get; }

		public string? LevelSetting { get; }

		public string? AmountSetting { get; }

		public string? EnabledSetting { get; }

		public RecipeSpec(string station, int level, int amount, string? cost, string? costSetting = null, string? levelSetting = null, string? amountSetting = null, string? enabledSetting = null)
		{
			Station = station;
			Level = level;
			Amount = amount;
			Cost = cost;
			CostSetting = costSetting;
			LevelSetting = levelSetting;
			AmountSetting = amountSetting;
			EnabledSetting = enabledSetting;
		}
	}
	public sealed class ItemDefinition
	{
		public string Prefab { get; }

		public string Word { get; }

		public string BaseItem { get; }

		public ItemKind Kind { get; }

		public BestiaryModule Module { get; }

		public float DamageFactor { get; }

		public RecipeSpec? Recipe { get; }

		public FoodStats Food { get; }

		public ItemDefinition(string prefab, string word, string baseItem, ItemKind kind, BestiaryModule module, float damageFactor = 1f, RecipeSpec? recipe = null, FoodStats food = default(FoodStats))
		{
			Prefab = prefab;
			Word = word;
			BaseItem = baseItem;
			Kind = kind;
			Module = module;
			DamageFactor = damageFactor;
			Recipe = recipe;
			Food = food;
		}
	}
	public static class ItemCatalog
	{
		public const float BoneDamageFactor = 0.85f;

		public const int BoneWeaponLevel = 2;

		private static readonly (string Key, string GameItem, string Cost)[] Weapons = new(string, string, string)[7]
		{
			("Dagger", "KnifeCopper", "BoneFragments:8:4"),
			("Sword", "SwordBronze", "ECP_Spine:1, BoneFragments:10:5"),
			("Axe", "AxeBronze", "ECP_Spine:1, BoneFragments:10:5"),
			("Mace", "MaceBronze", "ECP_Spine:1, BoneFragments:12:6"),
			("Spear", "SpearBronze", "ECP_Spine:1, BoneFragments:10:5"),
			("Atgeir", "AtgeirBronze", "ECP_Spine:2, BoneFragments:16:8"),
			("Bow", "BowFineWood", "ECP_Spine:1, BoneFragments:12:6")
		};

		public static IReadOnlyList<ItemDefinition> All { get; } = Build();

		public static IReadOnlyList<ItemDefinition> BoneWeapons { get; } = All.Where((ItemDefinition i) => i.Prefab.StartsWith("ECP_Bone") && i.Kind == ItemKind.Weapon && i.Prefab != "ECP_BoneCrossbow").ToArray();

		public static ItemDefinition? Find(string prefab)
		{
			return All.FirstOrDefault((ItemDefinition i) => i.Prefab == prefab);
		}

		private static ItemDefinition[] Build()
		{
			List<ItemDefinition> list = Weapons.Select(((string Key, string GameItem, string Cost) w) => new ItemDefinition("ECP_Bone" + w.Key, "item_ecp_bone" + w.Key.ToLowerInvariant(), w.GameItem, ItemKind.Weapon, BestiaryModule.ArsenalCrafting, 0.85f, new RecipeSpec("piece_workbench", 2, 1, w.Cost))).ToList();
			list.Add(new ItemDefinition("ECP_Spine", "item_ecp_spine", "BoneFragments", ItemKind.Material, BestiaryModule.Skeletons));
			list.Add(new ItemDefinition("ECP_ArrowBone", "item_ecp_arrowbone", "ArrowWood", ItemKind.Ammo, BestiaryModule.ArsenalCrafting, 1f, new RecipeSpec("piece_workbench", 2, 20, "BoneFragments:8")));
			list.Add(new ItemDefinition("ECP_BoneCrossbow", "item_ecp_bonecrossbow", "CrossbowArbalest", ItemKind.Weapon, BestiaryModule.ArsenalCrafting, 1f, new RecipeSpec("piece_workbench", 2, 1, null, "7 - Bone Crossbow.Recipe", "7 - Bone Crossbow.Workbench Level", null, "7 - Bone Crossbow.Craftable")));
			list.Add(new ItemDefinition("ECP_BoltBoneBlunt", "item_ecp_boltboneblunt", "BoltBone", ItemKind.Ammo, BestiaryModule.ArsenalCrafting, 1f, new RecipeSpec("piece_workbench", 2, 20, null, "7 - Bone Crossbow.Bolt Recipe", "7 - Bone Crossbow.Workbench Level", "7 - Bone Crossbow.Bolts Per Craft", "7 - Bone Crossbow.Craftable")));
			list.Add(new ItemDefinition("ECP_ExecutionerGreataxe", "item_ecp_executionergreataxe", "Battleaxe", ItemKind.Weapon, BestiaryModule.ArsenalCrafting, 1f, new RecipeSpec("piece_workbench", 3, 1, null, null, "26 - Executioner Greataxe.Workbench Level", null, "26 - Executioner Greataxe.Recipe")));
			list.Add(new ItemDefinition("ECP_ExecutionerAxehead", "item_ecp_executioneraxehead", "BoneFragments", ItemKind.Material, BestiaryModule.Crypt));
			list.Add(new ItemDefinition("ECP_KrakenBeak", "item_ecp_krakenbeak", "SerpentScale", ItemKind.Material, BestiaryModule.Sea));
			list.Add(new ItemDefinition("ECP_KrakenMeat", "item_ecp_krakenmeat", "SerpentMeat", ItemKind.Material, BestiaryModule.Sea));
			list.Add(new ItemDefinition("ECP_KrakenMeatCooked", "item_ecp_krakenmeatcooked", "SerpentMeatCooked", ItemKind.Food, BestiaryModule.Sea, 1f, null, new FoodStats(80f, 28f, 4f, 1800f)));
			list.Add(new ItemDefinition("ECP_ShieldKraken", "item_ecp_shieldkraken", "ShieldSilver", ItemKind.Shield, BestiaryModule.Sea, 1f, new RecipeSpec("forge", 3, 1, "FineWood:10:10, Silver:5:3, ECP_KrakenBeak:1")));
			return list.ToArray();
		}
	}
	public readonly struct CostEntry : IEquatable<CostEntry>
	{
		public string Item { get; }

		public int Amount { get; }

		public int PerLevel { get; }

		public CostEntry(string item, int amount, int perLevel)
		{
			Item = item;
			Amount = amount;
			PerLevel = perLevel;
		}

		public bool Equals(CostEntry other)
		{
			if (Item == other.Item && Amount == other.Amount)
			{
				return PerLevel == other.PerLevel;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is CostEntry other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (Item ?? "").GetHashCode() ^ (Amount * 397) ^ PerLevel;
		}

		public override string ToString()
		{
			return Item + ":" + Amount + ":" + PerLevel;
		}
	}
	public sealed class ParsedCost
	{
		public IReadOnlyList<CostEntry> Entries { get; }

		public IReadOnlyList<string> Rejected { get; }

		public ParsedCost(IReadOnlyList<CostEntry> entries, IReadOnlyList<string> rejected)
		{
			Entries = entries;
			Rejected = rejected;
		}
	}
	public static class RecipeText
	{
		public static ParsedCost Parse(string? text, Func<string, bool> knownItem)
		{
			List<CostEntry> list = new List<CostEntry>();
			List<string> list2 = new List<string>();
			string[] array = (text ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				string[] array2 = text2.Split(':');
				string text3 = ((array2.Length >= 2) ? array2[0].Trim() : null);
				if (text3 == null || !knownItem(text3) || !int.TryParse(array2[1], out var result))
				{
					if (text2.Length > 0)
					{
						list2.Add(text2);
					}
				}
				else
				{
					int result2;
					int perLevel = ((array2.Length >= 3 && int.TryParse(array2[2], out result2)) ? result2 : 0);
					list.Add(new CostEntry(text3, result, perLevel));
				}
			}
			return new ParsedCost(list, list2);
		}
	}
	public static class Recipes
	{
		public static IReadOnlyList<CostEntry> GreataxeCost(int spines, int bones, bool spineAvailable)
		{
			List<CostEntry> list = new List<CostEntry>();
			Add(list, "ECP_ExecutionerAxehead", 1);
			Add(list, spineAvailable ? "ECP_Spine" : "BoneFragments", spines);
			Add(list, "BoneFragments", bones);
			return list;
		}

		private static void Add(List<CostEntry> cost, string item, int amount)
		{
			if (amount > 0)
			{
				int num = cost.FindIndex((CostEntry c) => c.Item == item);
				if (num >= 0)
				{
					cost[num] = new CostEntry(item, cost[num].Amount + amount, 0);
				}
				else
				{
					cost.Add(new CostEntry(item, amount, 0));
				}
			}
		}
	}
	public enum RecipeStationOutcome
	{
		Enabled,
		Off,
		NoStation
	}
	public static class RecipeStationRule
	{
		public static RecipeStationOutcome Decide(bool active, bool stationFound)
		{
			if (!active)
			{
				return RecipeStationOutcome.Off;
			}
			if (!stationFound)
			{
				return RecipeStationOutcome.NoStation;
			}
			return RecipeStationOutcome.Enabled;
		}
	}
}
namespace ExtraPotions.Bestiary.Assets
{
	public sealed class ItemArt
	{
		public string Item { get; }

		public string Bundle { get; }

		public string Model { get; }

		public string Icon { get; }

		public ItemArt(string item, string bundle, string model, string icon)
		{
			Item = item;
			Bundle = bundle;
			Model = model;
			Icon = icon;
		}
	}
	public static class BundleCatalog
	{
		public static IReadOnlyList<string> Names { get; } = new string[8] { "ecr_cryptmimic", "ecr_slinger", "ecr_rimegiant", "ecp_kraken", "ecp_kraken_loot", "ecp_skel_arsenal", "ecp_crossbowman", "ecp_headsman" };

		public static IReadOnlyList<ItemArt> Items { get; } = Build();

		public static string Resource(string name, string platform)
		{
			return "Bestiary.bundles." + name + "." + platform;
		}

		public static ItemArt? FindItem(string item)
		{
			return Items.FirstOrDefault((ItemArt i) => i.Item == item);
		}

		private static ItemArt[] Build()
		{
			List<ItemArt> list = new List<ItemArt>();
			string[] array = new string[7] { "Dagger", "Sword", "Axe", "Mace", "Spear", "Atgeir", "Bow" };
			foreach (string text in array)
			{
				string text2 = "ecp_skel_" + text.ToLowerInvariant() + ((text == "Bow") ? "_player" : "");
				list.Add(new ItemArt("ECP_Bone" + text, "ecp_skel_arsenal", text2, text2 + "_icon"));
			}
			list.Add(new ItemArt("ECP_Spine", "ecp_skel_arsenal", "ecp_spine", "ecp_spine_icon"));
			list.Add(new ItemArt("ECP_ArrowBone", "ecp_skel_arsenal", "ecp_skel_arrow", "ecp_skel_arrow_icon"));
			list.Add(new ItemArt("ECP_BoneCrossbow", "ecp_crossbowman", "ecp_xbow_item_rig", "ecp_xbow_crossbow_icon"));
			list.Add(new ItemArt("ECP_BoltBoneBlunt", "ecp_crossbowman", "ecp_xbow_bolt", "ecp_xbow_bolt_icon"));
			list.Add(new ItemArt("ECP_ExecutionerGreataxe", "ecp_headsman", "ecp_greataxe_held", "ecp_greataxe_icon"));
			list.Add(new ItemArt("ECP_ExecutionerAxehead", "ecp_headsman", "ecp_greataxe_axehead", "ecp_greataxe_axehead_icon"));
			list.Add(new ItemArt("ECP_KrakenBeak", "ecp_kraken_loot", "ecp_kraken_beak", "ecp_kraken_beak_icon"));
			list.Add(new ItemArt("ECP_KrakenMeat", "ecp_kraken_loot", "ecp_kraken_meat", "ecp_kraken_meat_icon"));
			list.Add(new ItemArt("ECP_KrakenMeatCooked", "ecp_kraken_loot", "ecp_kraken_meat_cooked", "ecp_kraken_meat_cooked_icon"));
			list.Add(new ItemArt("ECP_ShieldKraken", "ecp_kraken_loot", "ecp_kraken_beak_shield", "ecp_kraken_beak_shield_icon"));
			return list.ToArray();
		}
	}
}