Decompiled source of Wildblood v0.5.7

plugins/Wildblood/Wildblood.Core.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using ExtraPotions.Wildblood.Bosses;
using ExtraPotions.Wildblood.Config;
using ExtraPotions.Wildblood.Conversion;
using ExtraPotions.Wildblood.Integration;
using ExtraPotions.Wildblood.Loot;
using ExtraPotions.Wildblood.Stars;
using ExtraPotions.Wildblood.Traits;
using ExtraPotions.Wildblood.Transfers;
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.5.7.0")]
[assembly: AssemblyInformationalVersion("0.5.7+fatal: not a git repository (or any of the parent directories): .git")]
[assembly: AssemblyProduct("Wildblood.Core")]
[assembly: AssemblyTitle("Wildblood.Core")]
[assembly: AssemblyVersion("0.5.7.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.Wildblood
{
	public interface IDice
	{
		float Value();
	}
	public static class DiceExtensions
	{
		public static bool Percent(this IDice dice, float chancePercent)
		{
			if (chancePercent <= 0f)
			{
				return false;
			}
			if (chancePercent >= 100f)
			{
				dice.Value();
				return true;
			}
			return dice.Value() * 100f < chancePercent;
		}

		public static int Range(this IDice dice, int min, int maxExclusive)
		{
			if (maxExclusive <= min)
			{
				dice.Value();
				return min;
			}
			float num = Math.Min(MathF.PI * 113f / 355f, Math.Max(0f, dice.Value()));
			return min + (int)Math.Floor(num * (float)(maxExclusive - min));
		}

		public static int PickWeighted(this IDice dice, float[] weights)
		{
			if (weights == null)
			{
				throw new ArgumentNullException("weights");
			}
			float num = 0f;
			foreach (float val in weights)
			{
				num += Math.Max(0f, val);
			}
			float num2 = Math.Min(MathF.PI * 113f / 355f, Math.Max(0f, dice.Value())) * num;
			if (num <= 0f)
			{
				return 0;
			}
			float num3 = 0f;
			int result = 0;
			for (int j = 0; j < weights.Length; j++)
			{
				if (!(weights[j] <= 0f))
				{
					result = j;
					num3 += weights[j];
					if (num2 < num3)
					{
						return j;
					}
				}
			}
			return result;
		}
	}
	public sealed class SeededDice : IDice
	{
		private readonly Random random;

		public SeededDice(int seed)
		{
			random = new Random(seed);
		}

		public float Value()
		{
			return (float)random.NextDouble();
		}
	}
	public static class SpawnListPass
	{
		public static bool SkipOriginal(bool eventSpawns, bool areaBlocked)
		{
			return !eventSpawns && areaBlocked;
		}

		public static bool HordeGrows(bool eventSpawns, bool hasSpawners, bool enabled, float multiplier, bool listSkipped)
		{
			if (!listSkipped && !eventSpawns && hasSpawners && enabled)
			{
				return multiplier > 1f;
			}
			return false;
		}
	}
	public static class WildbloodCore
	{
		public const string PluginGuid = "com.meadworks.wildblood";

		public const string ProductName = "Wildblood";

		public const string ProductVersion = "0.5.7";

		public const string Family = "Fang";

		public const string TokenNamespace = "wildblood";

		public const string TokenPrefix = "meadworks_wildblood_";

		public const string SupportedGameVersion = "1.0.17";
	}
}
namespace ExtraPotions.Wildblood.Visuals
{
	public static class FlameVisualPolicy
	{
		public const float DefaultLife = 7f;

		public const float DefaultRadius = 1f;

		public static float BodyScale(float radius)
		{
			return Math.Max(0.5f, radius * 2f) * 0.4f;
		}

		public static bool BodyVisible(bool cloaked, bool cloning)
		{
			if (!cloaked)
			{
				return !cloning;
			}
			return false;
		}

		public static bool DrawDecals(bool flamesAvailable, float density)
		{
			if (flamesAvailable)
			{
				return Density(density) <= 0f;
			}
			return true;
		}

		private static float Density(float value)
		{
			return Math.Max(0f, Math.Min(1f, value));
		}

		public static float PerPatchRate(float densityAdjustedRate, float density)
		{
			return densityAdjustedRate / Math.Max(Density(density), 0.01f) * 0.5f;
		}

		public static int Take(ref float owed, float rate, int patches, float elapsed, float density)
		{
			float num = (float)patches * elapsed * Density(density);
			owed = ((num > 0f) ? (owed + rate * num) : 0f);
			int num2 = (int)Math.Floor(owed);
			owed -= num2;
			return num2;
		}
	}
}
namespace ExtraPotions.Wildblood.Transfers
{
	public interface IInventoryImageStore
	{
		bool IsBound(string source);

		byte[] Capture(string source);

		void Apply(string source, byte[] image);

		void Save(string source);
	}
	public interface IInventoryCheckpointStore
	{
		bool ReceiptConfirmed(string source);
	}
	public interface IInventoryDecisionObserver
	{
		void DecisionFlushed(InventoryDecisionRecord record);
	}
	public interface IInventoryRecoveryDiagnostics
	{
		void RecoveryRequired(string code);
	}
	public sealed class DurableInventoryParticipant
	{
		private readonly InventoryDecisionJournal journal;

		private readonly IInventoryImageStore store;

		private readonly Func<double> clock;

		public DurableInventoryParticipant(InventoryDecisionJournal journal, IInventoryImageStore store, Func<double> monotonicClock)
		{
			this.journal = journal ?? throw new ArgumentNullException("journal");
			this.store = store ?? throw new ArgumentNullException("store");
			clock = monotonicClock ?? throw new ArgumentNullException("monotonicClock");
			foreach (InventoryDecisionRecord item in from r in journal.Snapshot()
				where r.State == InventoryDecision.Prepared
				select r)
			{
				journal.Transition(item, InventoryDecision.Aborted);
			}
		}

		public bool IsHeld(string source)
		{
			return journal.Snapshot().Any((InventoryDecisionRecord r) => r.Source == source && (r.State == InventoryDecision.Prepared || r.State == InventoryDecision.Decided || (r.State == InventoryDecision.Committed && store is IInventoryCheckpointStore inventoryCheckpointStore && !inventoryCheckpointStore.ReceiptConfirmed(source))));
		}

		public InventoryDecision Prepare(string id, string source, string fingerprint, byte[] before, byte[] after, byte[]? metadata = null)
		{
			Expire();
			InventoryDecisionRecord inventoryDecisionRecord = journal.Find(id);
			if (inventoryDecisionRecord != null)
			{
				if (!(inventoryDecisionRecord.Source == source) || !(inventoryDecisionRecord.Fingerprint == fingerprint) || !((ReadOnlySpan<byte>)inventoryDecisionRecord.Before).SequenceEqual((ReadOnlySpan<byte>)before) || !((ReadOnlySpan<byte>)inventoryDecisionRecord.After).SequenceEqual((ReadOnlySpan<byte>)after) || !((ReadOnlySpan<byte>)inventoryDecisionRecord.Metadata).SequenceEqual((ReadOnlySpan<byte>)(metadata ?? Array.Empty<byte>())))
				{
					return InventoryDecision.Rejected;
				}
				return Result(inventoryDecisionRecord);
			}
			if (!journal.CanAdmit || IsHeld(source) || !store.IsBound(source) || !((ReadOnlySpan<byte>)store.Capture(source)).SequenceEqual((ReadOnlySpan<byte>)before))
			{
				return InventoryDecision.Rejected;
			}
			try
			{
				journal.Prepare(id, source, fingerprint, clock() + 10.0, before, after, metadata);
				return InventoryDecision.Prepared;
			}
			catch
			{
				return InventoryDecision.Rejected;
			}
		}

		public InventoryDecision Status(string id)
		{
			Expire();
			InventoryDecisionRecord inventoryDecisionRecord = journal.Find(id);
			if (inventoryDecisionRecord != null)
			{
				return Result(inventoryDecisionRecord);
			}
			return InventoryDecision.Rejected;
		}

		private InventoryDecision Result(InventoryDecisionRecord record)
		{
			if (record.State != InventoryDecision.Decided && (record.State != InventoryDecision.Committed || !(store is IInventoryCheckpointStore inventoryCheckpointStore) || inventoryCheckpointStore.ReceiptConfirmed(record.Source)))
			{
				return record.State;
			}
			return InventoryDecision.RecoveryRequired;
		}

		public InventoryDecision Commit(string id)
		{
			Expire();
			InventoryDecisionRecord inventoryDecisionRecord = journal.Find(id);
			if (inventoryDecisionRecord == null)
			{
				return InventoryDecision.Rejected;
			}
			if (inventoryDecisionRecord.State == InventoryDecision.Aborted || (inventoryDecisionRecord.State == InventoryDecision.Committed && Result(inventoryDecisionRecord) == InventoryDecision.Committed))
			{
				return inventoryDecisionRecord.State;
			}
			try
			{
				if (inventoryDecisionRecord.State == InventoryDecision.Prepared)
				{
					if (!store.IsBound(inventoryDecisionRecord.Source))
					{
						journal.Transition(inventoryDecisionRecord, InventoryDecision.Decided);
						return Recovery("BindingUnavailable");
					}
					if (!((ReadOnlySpan<byte>)store.Capture(inventoryDecisionRecord.Source)).SequenceEqual((ReadOnlySpan<byte>)inventoryDecisionRecord.Before))
					{
						journal.Transition(inventoryDecisionRecord, InventoryDecision.Aborted);
						return InventoryDecision.Aborted;
					}
					journal.Transition(inventoryDecisionRecord, InventoryDecision.Decided);
				}
				if (!store.IsBound(inventoryDecisionRecord.Source))
				{
					return Recovery("BindingUnavailable");
				}
				(store as IInventoryDecisionObserver)?.DecisionFlushed(inventoryDecisionRecord);
				byte[] array = store.Capture(inventoryDecisionRecord.Source);
				if (((ReadOnlySpan<byte>)array).SequenceEqual((ReadOnlySpan<byte>)inventoryDecisionRecord.Before))
				{
					store.Apply(inventoryDecisionRecord.Source, inventoryDecisionRecord.After);
				}
				else if (!((ReadOnlySpan<byte>)array).SequenceEqual((ReadOnlySpan<byte>)inventoryDecisionRecord.After))
				{
					return Recovery("InventoryImageConflict");
				}
				if (!store.IsBound(inventoryDecisionRecord.Source) || !((ReadOnlySpan<byte>)store.Capture(inventoryDecisionRecord.Source)).SequenceEqual((ReadOnlySpan<byte>)inventoryDecisionRecord.After))
				{
					return Recovery("ImageNotApplied");
				}
				store.Save(inventoryDecisionRecord.Source);
				journal.Transition(inventoryDecisionRecord, InventoryDecision.Committed);
				return InventoryDecision.Committed;
			}
			catch (Exception ex)
			{
				return Recovery("Persistence" + ex.GetType().Name);
			}
		}

		private InventoryDecision Recovery(string code)
		{
			(store as IInventoryRecoveryDiagnostics)?.RecoveryRequired(code);
			return InventoryDecision.RecoveryRequired;
		}

		public InventoryDecision Rollback(string id)
		{
			InventoryDecisionRecord inventoryDecisionRecord = journal.Find(id);
			if (inventoryDecisionRecord == null)
			{
				return InventoryDecision.Rejected;
			}
			if (inventoryDecisionRecord.State == InventoryDecision.Prepared)
			{
				try
				{
					journal.Transition(inventoryDecisionRecord, InventoryDecision.Aborted);
				}
				catch
				{
					return InventoryDecision.RecoveryRequired;
				}
			}
			return Result(inventoryDecisionRecord);
		}

		public void Expire()
		{
			foreach (InventoryDecisionRecord item in from r in journal.Snapshot()
				where r.State == InventoryDecision.Prepared && clock() >= r.Deadline
				select r)
			{
				journal.Transition(item, InventoryDecision.Aborted);
			}
		}
	}
	public enum InventoryDecision
	{
		Rejected,
		Prepared,
		Decided,
		Committed,
		Aborted,
		RecoveryRequired
	}
	public sealed class InventoryDecisionRecord
	{
		private readonly byte[] before;

		private readonly byte[] after;

		private readonly byte[] metadata;

		public string Id { get; }

		public string Source { get; }

		public string Fingerprint { get; }

		public double Deadline { get; }

		public InventoryDecision State { get; internal set; }

		public byte[] Before => (byte[])before.Clone();

		public byte[] After => (byte[])after.Clone();

		public byte[] Metadata => (byte[])metadata.Clone();

		internal InventoryDecisionRecord(string id, string source, string fingerprint, double deadline, byte[] before, byte[] after, InventoryDecision state, byte[]? metadata = null)
		{
			Id = id;
			Source = source;
			Fingerprint = fingerprint;
			Deadline = deadline;
			this.before = (byte[])before.Clone();
			this.after = (byte[])after.Clone();
			this.metadata = ((metadata == null) ? Array.Empty<byte>() : ((byte[])metadata.Clone()));
			State = state;
		}
	}
	public sealed class InventoryDecisionJournal : IDisposable
	{
		private const int Magic = 1113018929;

		private const int MaxFrame = 4194304;

		private const long MaxFile = 268435456L;

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

		private readonly FileStream stream;

		private readonly int capacity;

		private bool disposed;

		private bool faulted;

		public bool CanAdmit
		{
			get
			{
				if (!disposed && !faulted && records.Count < capacity)
				{
					return stream.Length < 264241152;
				}
				return false;
			}
		}

		public InventoryDecisionJournal(string path, int capacity = 4096)
			: this(path, capacity, requireExisting: false)
		{
		}

		public InventoryDecisionJournal(string path, bool requireExisting)
			: this(path, 4096, requireExisting)
		{
		}

		public InventoryDecisionJournal(string path, int capacity, bool requireExisting)
		{
			if (capacity < 1 || capacity > 4096)
			{
				throw new ArgumentOutOfRangeException("capacity");
			}
			this.capacity = capacity;
			Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
			stream = new FileStream(path, requireExisting ? FileMode.Open : FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
			try
			{
				Read();
			}
			catch
			{
				stream.Dispose();
				throw;
			}
		}

		public InventoryDecisionRecord? Find(string id)
		{
			if (!records.TryGetValue(id, out InventoryDecisionRecord value))
			{
				return null;
			}
			return value;
		}

		public IReadOnlyList<InventoryDecisionRecord> Snapshot()
		{
			return Array.AsReadOnly(records.Values.ToArray());
		}

		public InventoryDecisionRecord Prepare(string id, string source, string fingerprint, double deadline, byte[] before, byte[] after, byte[]? metadata = null)
		{
			Text(id);
			Text(source);
			Text(fingerprint);
			if (!CanAdmit || records.ContainsKey(id) || double.IsNaN(deadline) || double.IsInfinity(deadline) || before == null || after == null || before.Length > 2080768 || after.Length > 2080768 || ((metadata != null) ? metadata.Length : 0) > 16384)
			{
				throw new InvalidOperationException("Journal admission denied.");
			}
			InventoryDecisionRecord inventoryDecisionRecord = new InventoryDecisionRecord(id, source, fingerprint, deadline, before, after, InventoryDecision.Prepared, metadata);
			Append(inventoryDecisionRecord);
			records.Add(id, inventoryDecisionRecord);
			return inventoryDecisionRecord;
		}

		public void Transition(InventoryDecisionRecord record, InventoryDecision next)
		{
			if (disposed || faulted || !records.TryGetValue(record.Id, out InventoryDecisionRecord value) || record != value)
			{
				throw new InvalidOperationException("Inactive journal record.");
			}
			if (!Allowed(record.State, next))
			{
				throw new InvalidOperationException("Invalid durable inventory transition.");
			}
			if (next != record.State)
			{
				InventoryDecisionRecord record2 = new InventoryDecisionRecord(record.Id, record.Source, record.Fingerprint, record.Deadline, record.Before, record.After, next, record.Metadata);
				Append(record2);
				record.State = next;
			}
		}

		private static bool Allowed(InventoryDecision old, InventoryDecision next)
		{
			if (old != next && (old != InventoryDecision.Prepared || (next != InventoryDecision.Decided && next != InventoryDecision.Aborted)))
			{
				if (old == InventoryDecision.Decided)
				{
					return next == InventoryDecision.Committed;
				}
				return false;
			}
			return true;
		}

		private static void Text(string value)
		{
			if (string.IsNullOrWhiteSpace(value) || value.Length > 512 || value.IndexOfAny(new char[3] { '\0', '\r', '\n' }) >= 0)
			{
				throw new InvalidDataException("Invalid journal identity.");
			}
		}

		private void Append(InventoryDecisionRecord record)
		{
			try
			{
				using MemoryStream memoryStream = new MemoryStream();
				using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
				{
					binaryWriter.Write(2);
					binaryWriter.Write(record.Id);
					binaryWriter.Write(record.Source);
					binaryWriter.Write(record.Fingerprint);
					binaryWriter.Write(record.Deadline);
					binaryWriter.Write((int)record.State);
					WriteImage(binaryWriter, record.Before);
					WriteImage(binaryWriter, record.After);
					WriteImage(binaryWriter, record.Metadata);
				}
				byte[] array = memoryStream.ToArray();
				if (array.Length > 4194304 || stream.Length + array.Length + 40 > 268435456)
				{
					throw new IOException("Journal storage capacity reached.");
				}
				using (BinaryWriter binaryWriter2 = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true))
				{
					binaryWriter2.Write(1113018929);
					binaryWriter2.Write(array.Length);
					binaryWriter2.Write(array);
					using (SHA256 sHA = SHA256.Create())
					{
						binaryWriter2.Write(sHA.ComputeHash(array));
					}
					binaryWriter2.Flush();
				}
				stream.Flush(flushToDisk: true);
			}
			catch
			{
				faulted = true;
				throw;
			}
		}

		private static void WriteImage(BinaryWriter writer, byte[] image)
		{
			writer.Write(image.Length);
			writer.Write(image);
		}

		private static byte[] ReadImage(BinaryReader reader)
		{
			int num = reader.ReadInt32();
			if (num < 0 || num > 2096128 || num > reader.BaseStream.Length - reader.BaseStream.Position)
			{
				throw new InvalidDataException("Invalid inventory image.");
			}
			return reader.ReadBytes(num);
		}

		private void Read()
		{
			if (stream.Length > 268435456)
			{
				throw new InvalidDataException("Journal too large.");
			}
			long complete = 0L;
			using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true))
			{
				while (stream.Position < stream.Length)
				{
					if (stream.Length - stream.Position < 8)
					{
						Truncate(complete);
						break;
					}
					if (binaryReader.ReadInt32() != 1113018929)
					{
						throw new InvalidDataException("Journal frame signature mismatch.");
					}
					int num = binaryReader.ReadInt32();
					if (num < 0 || num > 4194304)
					{
						throw new InvalidDataException("Journal frame exceeds bounds.");
					}
					if (stream.Length - stream.Position < (long)num + 32L)
					{
						Truncate(complete);
						break;
					}
					byte[] buffer = binaryReader.ReadBytes(num);
					byte[] array = binaryReader.ReadBytes(32);
					using (SHA256 sHA = SHA256.Create())
					{
						if (!((ReadOnlySpan<byte>)array).SequenceEqual((ReadOnlySpan<byte>)sHA.ComputeHash(buffer)))
						{
							throw new InvalidDataException("Journal frame checksum mismatch.");
						}
					}
					InventoryDecisionRecord inventoryDecisionRecord;
					try
					{
						using MemoryStream memoryStream = new MemoryStream(buffer);
						using BinaryReader binaryReader2 = new BinaryReader(memoryStream, Encoding.UTF8);
						int num2 = binaryReader2.ReadInt32();
						if (num2 != 1 && num2 != 2)
						{
							throw new InvalidDataException("Unknown journal version.");
						}
						string text = binaryReader2.ReadString();
						string text2 = binaryReader2.ReadString();
						string text3 = binaryReader2.ReadString();
						Text(text);
						Text(text2);
						Text(text3);
						double num3 = binaryReader2.ReadDouble();
						if (double.IsNaN(num3) || double.IsInfinity(num3))
						{
							throw new InvalidDataException("Invalid deadline.");
						}
						InventoryDecision inventoryDecision = (InventoryDecision)binaryReader2.ReadInt32();
						if (inventoryDecision != InventoryDecision.Prepared && inventoryDecision != InventoryDecision.Decided && inventoryDecision != InventoryDecision.Committed && inventoryDecision != InventoryDecision.Aborted)
						{
							throw new InvalidDataException("Invalid decision state.");
						}
						byte[] before = ReadImage(binaryReader2);
						byte[] after = ReadImage(binaryReader2);
						byte[] array2 = ((num2 == 2) ? ReadImage(binaryReader2) : Array.Empty<byte>());
						if (array2.Length > 16384)
						{
							throw new InvalidDataException("Journal metadata exceeds bounds.");
						}
						inventoryDecisionRecord = new InventoryDecisionRecord(text, text2, text3, num3, before, after, inventoryDecision, array2);
						if (memoryStream.Position != memoryStream.Length)
						{
							throw new InvalidDataException("Journal record has trailing bytes.");
						}
					}
					catch (EndOfStreamException innerException)
					{
						throw new InvalidDataException("Invalid complete journal frame.", innerException);
					}
					if (records.TryGetValue(inventoryDecisionRecord.Id, out InventoryDecisionRecord value))
					{
						if (inventoryDecisionRecord.Source != value.Source || inventoryDecisionRecord.Fingerprint != value.Fingerprint || inventoryDecisionRecord.Deadline != value.Deadline || !((ReadOnlySpan<byte>)inventoryDecisionRecord.Before).SequenceEqual((ReadOnlySpan<byte>)value.Before) || !((ReadOnlySpan<byte>)inventoryDecisionRecord.After).SequenceEqual((ReadOnlySpan<byte>)value.After) || !((ReadOnlySpan<byte>)inventoryDecisionRecord.Metadata).SequenceEqual((ReadOnlySpan<byte>)value.Metadata) || !Allowed(value.State, inventoryDecisionRecord.State))
						{
							throw new InvalidDataException("Conflicting journal history.");
						}
					}
					else if (records.Count >= capacity || inventoryDecisionRecord.State != InventoryDecision.Prepared)
					{
						throw new InvalidDataException("Invalid journal admission history.");
					}
					records[inventoryDecisionRecord.Id] = inventoryDecisionRecord;
					complete = stream.Position;
				}
			}
			stream.Position = stream.Length;
		}

		private void Truncate(long complete)
		{
			stream.SetLength(complete);
			stream.Position = complete;
			stream.Flush(flushToDisk: true);
		}

		public void Dispose()
		{
			if (!disposed)
			{
				disposed = true;
				stream.Dispose();
			}
		}
	}
	public enum PouchDropDecision
	{
		Create,
		Created,
		Unknown,
		Unavailable
	}
	public enum PouchDropState
	{
		None,
		Marked,
		Created
	}
	public static class PouchDropKeys
	{
		public static string Entry(long sourceUser, uint sourceId, long stealer, int counter, string root, byte[] item)
		{
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			using SHA256 sHA = SHA256.Create();
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write("wb.pouch.drop.v1");
				binaryWriter.Write(sourceUser);
				binaryWriter.Write(sourceId);
				binaryWriter.Write(stealer);
				binaryWriter.Write(counter);
				binaryWriter.Write(root ?? "");
				binaryWriter.Write(item.Length);
				binaryWriter.Write(item);
			}
			return Hex(sHA.ComputeHash(memoryStream.ToArray()));
		}

		internal static string Hex(byte[] bytes)
		{
			return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
		}

		public static bool IsKey(string? value)
		{
			if (value != null && value.Length == 64)
			{
				return value.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
			}
			return false;
		}
	}
	public sealed class PouchDropLedger : IDisposable
	{
		private const string Source = "wildblood.pouch.drop";

		private const string CreatedSuffix = ".created";

		private readonly string directory;

		private readonly int segmentCapacity;

		private readonly int maxSegments;

		private readonly List<InventoryDecisionJournal> segments = new List<InventoryDecisionJournal>();

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

		public IReadOnlyList<string> Unresolved => records.Keys.Where((string k) => !k.EndsWith(".created", StringComparison.Ordinal) && !records.ContainsKey(k + ".created")).ToArray();

		public PouchDropLedger(string directory, int segmentCapacity = 4096, int maxSegments = 64)
		{
			if (segmentCapacity < 2 || segmentCapacity > 4096)
			{
				throw new ArgumentOutOfRangeException("segmentCapacity");
			}
			if (maxSegments < 1 || maxSegments > 1024)
			{
				throw new ArgumentOutOfRangeException("maxSegments");
			}
			this.directory = directory;
			this.segmentCapacity = segmentCapacity;
			this.maxSegments = maxSegments;
			try
			{
				Directory.CreateDirectory(directory);
				for (int i = 0; i < maxSegments && File.Exists(SegmentPath(i)); i++)
				{
					Open(i);
				}
				if (segments.Count == 0)
				{
					Open(0);
				}
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		private string SegmentPath(int index)
		{
			return Path.Combine(directory, "drops-" + index.ToString("0000") + ".bin");
		}

		private void Open(int index)
		{
			InventoryDecisionJournal inventoryDecisionJournal = new InventoryDecisionJournal(SegmentPath(index), segmentCapacity);
			segments.Add(inventoryDecisionJournal);
			foreach (InventoryDecisionRecord item in inventoryDecisionJournal.Snapshot())
			{
				if (records.ContainsKey(item.Id))
				{
					throw new InvalidDataException("Duplicate pouch drop decision.");
				}
				records.Add(item.Id, item);
			}
		}

		public PouchDropState State(string key)
		{
			if (records.ContainsKey(key + ".created"))
			{
				return PouchDropState.Created;
			}
			if (!records.ContainsKey(key))
			{
				return PouchDropState.None;
			}
			return PouchDropState.Marked;
		}

		public byte[]? MarkedItem(string key)
		{
			if (!records.TryGetValue(key, out InventoryDecisionRecord value))
			{
				return null;
			}
			return value.Before;
		}

		public string? CreatedIdentity(string key)
		{
			if (!records.TryGetValue(key + ".created", out InventoryDecisionRecord value))
			{
				return null;
			}
			return Encoding.UTF8.GetString(value.Metadata);
		}

		public PouchDropDecision Begin(string key, bool markedElsewhere, byte[]? item = null)
		{
			if (!PouchDropKeys.IsKey(key))
			{
				throw new ArgumentException("Pouch drop key.", "key");
			}
			switch (State(key))
			{
			case PouchDropState.Created:
				return PouchDropDecision.Created;
			case PouchDropState.Marked:
				return PouchDropDecision.Unknown;
			default:
			{
				if (markedElsewhere)
				{
					return PouchDropDecision.Unknown;
				}
				InventoryDecisionJournal inventoryDecisionJournal = Writable();
				if (inventoryDecisionJournal == null)
				{
					return PouchDropDecision.Unavailable;
				}
				InventoryDecisionRecord value = inventoryDecisionJournal.Prepare(key, "wildblood.pouch.drop", key, 0.0, item ?? Array.Empty<byte>(), Array.Empty<byte>());
				records.Add(key, value);
				return PouchDropDecision.Create;
			}
			}
		}

		public bool Complete(string key, string identity)
		{
			if (string.IsNullOrWhiteSpace(identity) || identity.Length > 128)
			{
				return false;
			}
			string text = CreatedIdentity(key);
			if (text != null)
			{
				return text == identity;
			}
			if (!records.ContainsKey(key))
			{
				return false;
			}
			InventoryDecisionJournal inventoryDecisionJournal = Writable();
			if (inventoryDecisionJournal == null)
			{
				return false;
			}
			InventoryDecisionRecord inventoryDecisionRecord = inventoryDecisionJournal.Prepare(key + ".created", "wildblood.pouch.drop", key, 0.0, Array.Empty<byte>(), Array.Empty<byte>(), Encoding.UTF8.GetBytes(identity));
			records.Add(inventoryDecisionRecord.Id, inventoryDecisionRecord);
			return true;
		}

		private InventoryDecisionJournal? Writable()
		{
			InventoryDecisionJournal inventoryDecisionJournal = segments[segments.Count - 1];
			if (inventoryDecisionJournal.CanAdmit)
			{
				return inventoryDecisionJournal;
			}
			if (segments.Count >= maxSegments)
			{
				return null;
			}
			Open(segments.Count);
			if (!segments[segments.Count - 1].CanAdmit)
			{
				return null;
			}
			return segments[segments.Count - 1];
		}

		public void Dispose()
		{
			foreach (InventoryDecisionJournal segment in segments)
			{
				segment.Dispose();
			}
			segments.Clear();
			records.Clear();
		}
	}
	public static class PouchDropMarker
	{
		public const int MaxKeys = 64;

		public static byte[] Encode(IEnumerable<string> keys)
		{
			List<string> list = keys.ToList();
			if (list.Count > 64 || list.Any((string k) => !PouchDropKeys.IsKey(k)) || list.Distinct<string>(StringComparer.Ordinal).Count() != list.Count)
			{
				throw new InvalidDataException("Pouch drop marker.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(1);
				binaryWriter.Write(list.Count);
				foreach (string item in list)
				{
					binaryWriter.Write(item);
				}
			}
			return memoryStream.ToArray();
		}

		public static IReadOnlyList<string> Decode(byte[]? bytes)
		{
			if (bytes == null || bytes.Length == 0)
			{
				return Array.Empty<string>();
			}
			if (bytes.Length > 4168)
			{
				throw new InvalidDataException("Pouch drop marker exceeds its bound.");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes, writable: false), Encoding.UTF8);
			try
			{
				if (binaryReader.ReadInt32() != 1)
				{
					throw new InvalidDataException("Pouch drop marker version.");
				}
				int num = binaryReader.ReadInt32();
				if (num < 0 || num > 64)
				{
					throw new InvalidDataException("Pouch drop marker count.");
				}
				List<string> list = new List<string>(num);
				for (int i = 0; i < num; i++)
				{
					string text = binaryReader.ReadString();
					if (!PouchDropKeys.IsKey(text) || list.Contains(text))
					{
						throw new InvalidDataException("Pouch drop marker key.");
					}
					list.Add(text);
				}
				if (binaryReader.BaseStream.Position != bytes.Length)
				{
					throw new InvalidDataException("Pouch drop marker trailing data.");
				}
				return list;
			}
			catch (EndOfStreamException)
			{
				throw new InvalidDataException("Pouch drop marker truncated.");
			}
		}
	}
	public enum TheftStatusAnswer : byte
	{
		Unknown,
		Approved,
		Denied
	}
	public sealed class TheftAsk
	{
		public string Root { get; }

		public int Counter { get; }

		public double SentAt { get; }

		public string Fingerprint { get; }

		public byte[] Item { get; }

		public TheftAsk(string root, int counter, string fingerprint, byte[] item, double sentAt)
		{
			Root = root;
			Counter = counter;
			Fingerprint = fingerprint;
			Item = item;
			SentAt = sentAt;
		}
	}
	public sealed class TheftStatusQuery
	{
		public string Root { get; }

		public string Fingerprint { get; }

		public TheftStatusAnswer Answer { get; }

		public TheftStatusQuery(string root, string fingerprint, TheftStatusAnswer answer)
		{
			Root = root;
			Fingerprint = fingerprint;
			Answer = answer;
		}
	}
	public static class TheftWire
	{
		public const int Version = 2;

		public const int MaxItem = 1048576;

		public const double AskWindowSeconds = 5.0;

		public const double ReplyDeadlineSeconds = 10.0;

		private const byte Ask = 4;

		private const byte Status = 2;

		private const byte Answer = 3;

		public static bool Fresh(double sentAt, double now)
		{
			if (Finite(sentAt) && Finite(now) && now - sentAt <= 5.0)
			{
				return sentAt - now <= 5.0;
			}
			return false;
		}

		private static bool Finite(double value)
		{
			if (!double.IsNaN(value) && !double.IsInfinity(value))
			{
				return value >= 0.0;
			}
			return false;
		}

		public static bool IsRoot(string? value)
		{
			return Hex(value, 32);
		}

		public static bool IsFingerprint(string? value)
		{
			return Hex(value, 64);
		}

		private static bool Hex(string? value, int length)
		{
			if (value != null && value.Length == length)
			{
				return value.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
			}
			return false;
		}

		public static bool ItemMatches(string fingerprint, byte[] item)
		{
			if (item != null && item.Length != 0)
			{
				return TheftMetadata.Hash(item) == fingerprint;
			}
			return false;
		}

		public static byte[] EncodeAsk(string root, int counter, string fingerprint, byte[] item, double sentAt)
		{
			if (!IsRoot(root) || counter <= 0 || !IsFingerprint(fingerprint) || item == null || item.Length == 0 || item.Length > 1048576 || !Finite(sentAt))
			{
				throw new InvalidDataException("Theft ask.");
			}
			return Write(delegate(BinaryWriter w)
			{
				w.Write((byte)4);
				w.Write(root);
				w.Write(counter);
				w.Write(sentAt);
				w.Write(fingerprint);
				w.Write(item.Length);
				w.Write(item);
			});
		}

		public static TheftAsk DecodeAsk(byte[] bytes)
		{
			return Read(bytes, 4, 1048832, delegate(BinaryReader r)
			{
				string text = r.ReadString();
				int num = r.ReadInt32();
				double num2 = r.ReadDouble();
				string text2 = r.ReadString();
				int num3 = r.ReadInt32();
				if (!IsRoot(text) || num <= 0 || !Finite(num2) || !IsFingerprint(text2) || num3 <= 0 || num3 > 1048576)
				{
					throw new InvalidDataException("Theft ask.");
				}
				byte[] array = r.ReadBytes(num3);
				if (array.Length != num3)
				{
					throw new InvalidDataException("Theft ask truncated.");
				}
				return new TheftAsk(text, num, text2, array, num2);
			});
		}

		public static byte[] EncodeStatus(string root, string fingerprint)
		{
			return Identity(2, root, fingerprint, null);
		}

		public static byte[] EncodeAnswer(string root, string fingerprint, TheftStatusAnswer answer)
		{
			return Identity(3, root, fingerprint, answer);
		}

		public static TheftStatusQuery DecodeStatus(byte[] bytes)
		{
			return Read(bytes, 2, 256, (BinaryReader r) => ReadIdentity(r, answer: false));
		}

		public static TheftStatusQuery DecodeAnswer(byte[] bytes)
		{
			return Read(bytes, 3, 256, (BinaryReader r) => ReadIdentity(r, answer: true));
		}

		private static byte[] Identity(byte kind, string root, string fingerprint, TheftStatusAnswer? answer)
		{
			if (!IsRoot(root) || !IsFingerprint(fingerprint) || (answer.HasValue && !Enum.IsDefined(typeof(TheftStatusAnswer), answer.Value)))
			{
				throw new InvalidDataException("Theft status identity.");
			}
			return Write(delegate(BinaryWriter w)
			{
				w.Write(kind);
				w.Write(root);
				w.Write(fingerprint);
				if (answer.HasValue)
				{
					w.Write((byte)answer.Value);
				}
			});
		}

		private static TheftStatusQuery ReadIdentity(BinaryReader r, bool answer)
		{
			string text = r.ReadString();
			string text2 = r.ReadString();
			TheftStatusAnswer theftStatusAnswer = (TheftStatusAnswer)(answer ? r.ReadByte() : 0);
			if (!IsRoot(text) || !IsFingerprint(text2) || !Enum.IsDefined(typeof(TheftStatusAnswer), theftStatusAnswer))
			{
				throw new InvalidDataException("Theft status identity.");
			}
			return new TheftStatusQuery(text, text2, theftStatusAnswer);
		}

		private static byte[] Write(Action<BinaryWriter> body)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(2);
				body(binaryWriter);
			}
			return memoryStream.ToArray();
		}

		private static T Read<T>(byte[] bytes, byte kind, int bound, Func<BinaryReader, T> body)
		{
			if (bytes == null || bytes.Length == 0 || bytes.Length > bound)
			{
				throw new InvalidDataException("Theft message bound.");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes, writable: false), Encoding.UTF8);
			try
			{
				if (binaryReader.ReadInt32() != 2 || binaryReader.ReadByte() != kind)
				{
					throw new InvalidDataException("Theft message kind.");
				}
				T result = body(binaryReader);
				if (binaryReader.BaseStream.Position != bytes.Length)
				{
					throw new InvalidDataException("Theft message trailing data.");
				}
				return result;
			}
			catch (EndOfStreamException)
			{
				throw new InvalidDataException("Theft message truncated.");
			}
		}
	}
	public sealed class TheftOutcomeArchive
	{
		public const int Capacity = 32;

		private readonly List<(string Root, string Fingerprint, bool Approved)> entries = new List<(string, string, bool)>();

		public int Count => entries.Count;

		public bool CanRecord => entries.Count < 32;

		public IReadOnlyList<(string Root, string Fingerprint, bool Approved)> Entries => entries.AsReadOnly();

		public TheftStatusAnswer Find(string root, string fingerprint)
		{
			foreach (var entry in entries)
			{
				if (entry.Root == root)
				{
					return (!(entry.Fingerprint != fingerprint)) ? (entry.Approved ? TheftStatusAnswer.Approved : TheftStatusAnswer.Denied) : TheftStatusAnswer.Unknown;
				}
			}
			return TheftStatusAnswer.Unknown;
		}

		public bool Contains(string root)
		{
			return entries.Any<(string, string, bool)>(((string Root, string Fingerprint, bool Approved) e) => e.Root == root);
		}

		public bool Record(string root, string fingerprint, bool approved)
		{
			if (!TheftWire.IsRoot(root) || !TheftWire.IsFingerprint(fingerprint))
			{
				return false;
			}
			foreach (var entry in entries)
			{
				if (entry.Root == root)
				{
					return entry.Fingerprint == fingerprint && entry.Approved == approved;
				}
			}
			if (!CanRecord)
			{
				return false;
			}
			entries.Add((root, fingerprint, approved));
			return true;
		}

		public bool Acknowledge(string root, string fingerprint)
		{
			int num = entries.FindIndex(((string Root, string Fingerprint, bool Approved) e) => e.Root == root && e.Fingerprint == fingerprint);
			if (num < 0)
			{
				return false;
			}
			entries.RemoveAt(num);
			return true;
		}

		public byte[] Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(1);
				binaryWriter.Write(entries.Count);
				foreach (var entry in entries)
				{
					binaryWriter.Write(entry.Root);
					binaryWriter.Write(entry.Fingerprint);
					binaryWriter.Write(entry.Approved);
				}
			}
			return memoryStream.ToArray();
		}

		public static TheftOutcomeArchive Decode(byte[]? bytes)
		{
			TheftOutcomeArchive theftOutcomeArchive = new TheftOutcomeArchive();
			if (bytes == null || bytes.Length == 0)
			{
				return theftOutcomeArchive;
			}
			if (bytes.Length > 3208)
			{
				throw new InvalidDataException("Theft outcome archive exceeds its bound.");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes, writable: false), Encoding.UTF8);
			try
			{
				if (binaryReader.ReadInt32() != 1)
				{
					throw new InvalidDataException("Theft outcome archive version.");
				}
				int num = binaryReader.ReadInt32();
				if (num < 0 || num > 32)
				{
					throw new InvalidDataException("Theft outcome archive count.");
				}
				for (int i = 0; i < num; i++)
				{
					string text = binaryReader.ReadString();
					string text2 = binaryReader.ReadString();
					bool item = binaryReader.ReadBoolean();
					if (!TheftWire.IsRoot(text) || !TheftWire.IsFingerprint(text2) || theftOutcomeArchive.Contains(text))
					{
						throw new InvalidDataException("Theft outcome archive row.");
					}
					theftOutcomeArchive.entries.Add((text, text2, item));
				}
				if (binaryReader.BaseStream.Position != bytes.Length)
				{
					throw new InvalidDataException("Theft outcome archive trailing data.");
				}
				return theftOutcomeArchive;
			}
			catch (EndOfStreamException)
			{
				throw new InvalidDataException("Theft outcome archive truncated.");
			}
		}
	}
	public static class TheftOwnerStatus
	{
		public static TheftStatusAnswer Answer(TheftOutcomeArchive archive, Func<string, string, bool> pouchHolds, string root, string fingerprint)
		{
			if (!TheftWire.IsRoot(root) || !TheftWire.IsFingerprint(fingerprint))
			{
				return TheftStatusAnswer.Unknown;
			}
			if (archive.Contains(root))
			{
				return archive.Find(root, fingerprint);
			}
			if (!pouchHolds(root, fingerprint))
			{
				return TheftStatusAnswer.Unknown;
			}
			return TheftStatusAnswer.Approved;
		}

		public static TheftStatusAnswer Decide(TheftOutcomeArchive archive, Func<string, string, bool> pouchHolds, string root, string fingerprint, out bool recorded)
		{
			recorded = false;
			TheftStatusAnswer theftStatusAnswer = Answer(archive, pouchHolds, root, fingerprint);
			if (theftStatusAnswer != TheftStatusAnswer.Unknown || !TheftWire.IsRoot(root) || !TheftWire.IsFingerprint(fingerprint) || archive.Contains(root))
			{
				return theftStatusAnswer;
			}
			if (!archive.Record(root, fingerprint, approved: false))
			{
				return TheftStatusAnswer.Unknown;
			}
			recorded = true;
			return TheftStatusAnswer.Denied;
		}
	}
	public static class TheftWorldArchive
	{
		public const string Prefix = "wildblood_theft_";

		public const int Capacity = 256;

		public static string Key(string root)
		{
			return "wildblood_theft_" + root;
		}

		public static string Entry(string root, string fingerprint, bool approved)
		{
			if (!TheftWire.IsRoot(root) || !TheftWire.IsFingerprint(fingerprint))
			{
				throw new InvalidDataException("Theft world archive row.");
			}
			return Key(root) + " " + Value(fingerprint, approved);
		}

		public static string Value(string fingerprint, bool approved)
		{
			return fingerprint + (approved ? "1" : "0");
		}

		public static TheftStatusAnswer Read(string? value, string fingerprint)
		{
			if (value == null || value.Length != 65 || !TheftWire.IsFingerprint(fingerprint) || !value.StartsWith(fingerprint, StringComparison.Ordinal))
			{
				return TheftStatusAnswer.Unknown;
			}
			if (value[64] != '1')
			{
				if (value[64] != '0')
				{
					return TheftStatusAnswer.Unknown;
				}
				return TheftStatusAnswer.Denied;
			}
			return TheftStatusAnswer.Approved;
		}

		public static IReadOnlyList<string> Rows(TheftOutcomeArchive archive)
		{
			return archive.Entries.Select<(string, string, bool), string>(((string Root, string Fingerprint, bool Approved) e) => Entry(e.Root, e.Fingerprint, e.Approved)).ToArray();
		}

		public static bool Admit(int existing, int adding, bool lastChance)
		{
			if (!(adding == 0 || lastChance))
			{
				return existing + adding <= 256;
			}
			return true;
		}
	}
	public enum TheftRecoveryStep
	{
		Retain,
		Record,
		Settled
	}
	public static class TheftRecovery
	{
		public static bool Eligible(TheftRequest request, bool v2)
		{
			if (v2 && request != null && TheftWire.IsRoot(request.Root))
			{
				return TheftWire.IsFingerprint(request.Fingerprint);
			}
			return false;
		}

		public static TheftRecoveryStep Step(TheftStatusAnswer answer, bool outcomeKnown)
		{
			if (!outcomeKnown)
			{
				if (answer != TheftStatusAnswer.Unknown)
				{
					return TheftRecoveryStep.Record;
				}
				return TheftRecoveryStep.Retain;
			}
			return TheftRecoveryStep.Settled;
		}

		public static TheftReceipt? Receipt(TheftRequest request, TheftStatusAnswer answer)
		{
			if (answer != TheftStatusAnswer.Unknown)
			{
				return TheftReceipt.For(request, answer == TheftStatusAnswer.Approved);
			}
			return null;
		}
	}
	public sealed class TheftIntent
	{
		public readonly InventoryDecisionRecord Record;

		private readonly byte[] request;

		public TheftRequest Request => TheftMetadata.Decode(request);

		public string Root => Record.Id;

		public int WireVersion { get; }

		internal TheftIntent(InventoryDecisionRecord record)
		{
			Record = record;
			using (MemoryStream memoryStream = new MemoryStream(record.Metadata, writable: false))
			{
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
				WireVersion = binaryReader.ReadInt32();
				if (WireVersion != 1 && WireVersion != 2)
				{
					throw new InvalidDataException("Requester intent version.");
				}
				request = binaryReader.ReadBytes(checked((int)(memoryStream.Length - memoryStream.Position)));
			}
			if (Request.Root != record.Id || TheftMetadata.Hash(request) != record.Fingerprint)
			{
				throw new InvalidDataException("Requester intent identity.");
			}
		}
	}
	public sealed class TheftClientLedger : IDisposable
	{
		private readonly InventoryDecisionJournal intents;

		private readonly InventoryDecisionJournal outcomes;

		private readonly InventoryDecisionJournal debits;

		private readonly InventoryDecisionJournal settlements;

		public IReadOnlyList<TheftIntent> All => (from r in intents.Snapshot()
			select new TheftIntent(r)).ToArray();

		public IReadOnlyList<TheftIntent> Incomplete => All.Where((TheftIntent i) => i.Record.State != InventoryDecision.Committed).ToArray();

		public bool CanAdmit
		{
			get
			{
				if (intents.CanAdmit && outcomes.CanAdmit && debits.CanAdmit && settlements.CanAdmit)
				{
					return Incomplete.Count == 0;
				}
				return false;
			}
		}

		public TheftClientLedger(string directory)
		{
			try
			{
				intents = new InventoryDecisionJournal(Path.Combine(directory, "intent.bin"));
				outcomes = new InventoryDecisionJournal(Path.Combine(directory, "outcome.bin"));
				debits = new InventoryDecisionJournal(Path.Combine(directory, "debit.bin"));
				settlements = new InventoryDecisionJournal(Path.Combine(directory, "settlement.bin"));
				InventoryDecisionJournal[] array = new InventoryDecisionJournal[2] { debits, settlements };
				foreach (InventoryDecisionJournal inventoryDecisionJournal in array)
				{
					foreach (InventoryDecisionRecord item in inventoryDecisionJournal.Snapshot())
					{
						if (item.State == InventoryDecision.Prepared)
						{
							inventoryDecisionJournal.Transition(item, InventoryDecision.Decided);
						}
					}
				}
				foreach (TheftIntent item2 in All)
				{
					if (item2.Record.State == InventoryDecision.Aborted)
					{
						throw new InvalidDataException("Requester intent was aborted.");
					}
					if (item2.Record.State == InventoryDecision.Committed && Settlement(item2) == null)
					{
						throw new InvalidDataException("Requester settlement missing.");
					}
				}
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public TheftIntent Admit(TheftRequest request, byte[] before, byte[] escrow, int wireVersion = 2)
		{
			if (wireVersion != 1 && wireVersion != 2)
			{
				throw new ArgumentOutOfRangeException("wireVersion");
			}
			if (!CanAdmit)
			{
				throw new InvalidOperationException("Requester already holds inventory or ledger is full.");
			}
			byte[] array = TheftMetadata.Encode(request);
			TheftMetadata.Decode(array);
			byte[] array2;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
				{
					binaryWriter.Write(wireVersion);
					binaryWriter.Write(array);
				}
				array2 = memoryStream.ToArray();
			}
			if (array2.Length > 2048)
			{
				throw new InvalidDataException("Requester intent exceeds bound.");
			}
			return new TheftIntent(intents.Prepare(request.Root, request.Root, TheftMetadata.Hash(array), 0.0, before, escrow, array2));
		}

		public bool HasOutcome(TheftIntent intent)
		{
			return outcomes.Find(intent.Root) != null;
		}

		public TheftReceipt Outcome(TheftIntent intent)
		{
			TheftReceipt theftReceipt = TheftMetadata.Receipt((outcomes.Find(intent.Root) ?? throw new InvalidOperationException("No authoritative outcome.")).Metadata);
			if (!theftReceipt.Matches(intent.Request) || theftReceipt.Disposition == TheftDisposition.Recovery)
			{
				throw new InvalidDataException("Unsettleable outcome.");
			}
			return theftReceipt;
		}

		public bool RecordOutcome(TheftIntent intent, TheftReceipt receipt)
		{
			if (receipt == null || !receipt.Matches(intent.Request) || receipt.Disposition == TheftDisposition.Recovery)
			{
				return false;
			}
			byte[] array = TheftMetadata.Encode(receipt);
			TheftMetadata.Receipt(array);
			InventoryDecisionRecord inventoryDecisionRecord = outcomes.Find(intent.Root);
			if (inventoryDecisionRecord != null)
			{
				return ((ReadOnlySpan<byte>)inventoryDecisionRecord.Metadata).SequenceEqual((ReadOnlySpan<byte>)array);
			}
			if (intent.Record.State == InventoryDecision.Committed)
			{
				return false;
			}
			InventoryDecisionRecord record = outcomes.Prepare(intent.Root, intent.Root, TheftMetadata.Hash(array), 0.0, Array.Empty<byte>(), Array.Empty<byte>(), array);
			outcomes.Transition(record, InventoryDecision.Decided);
			return true;
		}

		public InventoryDecisionRecord? Settlement(TheftIntent intent)
		{
			return settlements.Find(intent.Root);
		}

		public void PrepareSettlement(TheftIntent intent, byte[] before, byte[] after)
		{
			TheftReceipt receipt = Outcome(intent);
			if (Settlement(intent) != null)
			{
				throw new InvalidOperationException("Settlement image is already immutable.");
			}
			InventoryDecisionRecord record = settlements.Prepare(intent.Root, "wildblood.theft.settle." + intent.Root, TheftMetadata.Hash(TheftMetadata.Encode(receipt)), 0.0, before, after);
			settlements.Transition(record, InventoryDecision.Decided);
		}

		public InventoryDecision CommitDebit(TheftIntent intent, Func<InventoryDecisionJournal, IInventoryImageStore> storeFactory)
		{
			InventoryDecisionRecord inventoryDecisionRecord = debits.Find(intent.Root);
			if (inventoryDecisionRecord == null)
			{
				inventoryDecisionRecord = debits.Prepare(intent.Root, "wildblood.theft.debit." + intent.Root, intent.Record.Fingerprint, 0.0, intent.Record.Before, intent.Record.After);
				debits.Transition(inventoryDecisionRecord, InventoryDecision.Decided);
			}
			return Commit(debits, inventoryDecisionRecord, storeFactory);
		}

		public InventoryDecision CommitSettlement(TheftIntent intent, Func<InventoryDecisionJournal, IInventoryImageStore> storeFactory)
		{
			Outcome(intent);
			InventoryDecisionRecord inventoryDecisionRecord = Settlement(intent);
			if (inventoryDecisionRecord == null)
			{
				return InventoryDecision.RecoveryRequired;
			}
			InventoryDecision num = Commit(settlements, inventoryDecisionRecord, storeFactory);
			if (num == InventoryDecision.Committed)
			{
				InventoryDecisionRecord inventoryDecisionRecord2 = outcomes.Find(intent.Root) ?? throw new InvalidDataException("Requester outcome missing.");
				if (inventoryDecisionRecord2.State == InventoryDecision.Prepared)
				{
					outcomes.Transition(inventoryDecisionRecord2, InventoryDecision.Decided);
				}
				outcomes.Transition(inventoryDecisionRecord2, InventoryDecision.Committed);
				if (intent.Record.State == InventoryDecision.Prepared)
				{
					intents.Transition(intent.Record, InventoryDecision.Decided);
				}
				intents.Transition(intent.Record, InventoryDecision.Committed);
			}
			return num;
		}

		public InventoryDecision Reconcile(TheftIntent intent, Func<InventoryDecisionJournal, IInventoryImageStore> storeFactory)
		{
			InventoryDecisionRecord inventoryDecisionRecord = Settlement(intent);
			if (inventoryDecisionRecord != null && storeFactory(settlements) is IInventoryCheckpointStore inventoryCheckpointStore && inventoryCheckpointStore.ReceiptConfirmed(inventoryDecisionRecord.Source))
			{
				return CommitSettlement(intent, storeFactory);
			}
			InventoryDecision inventoryDecision = CommitDebit(intent, storeFactory);
			if (inventoryDecision == InventoryDecision.Committed)
			{
				if (inventoryDecisionRecord != null)
				{
					return CommitSettlement(intent, storeFactory);
				}
				return InventoryDecision.Prepared;
			}
			return inventoryDecision;
		}

		public bool FinalReceiptConfirmed(TheftIntent intent, Func<InventoryDecisionJournal, IInventoryImageStore> storeFactory)
		{
			InventoryDecisionRecord inventoryDecisionRecord = Settlement(intent);
			if (inventoryDecisionRecord != null && inventoryDecisionRecord.State == InventoryDecision.Committed && storeFactory(settlements) is IInventoryCheckpointStore inventoryCheckpointStore)
			{
				return inventoryCheckpointStore.ReceiptConfirmed(inventoryDecisionRecord.Source);
			}
			return false;
		}

		private static InventoryDecision Commit(InventoryDecisionJournal journal, InventoryDecisionRecord record, Func<InventoryDecisionJournal, IInventoryImageStore> storeFactory)
		{
			if (record.State == InventoryDecision.Prepared)
			{
				journal.Transition(record, InventoryDecision.Decided);
			}
			IInventoryImageStore inventoryImageStore = storeFactory(journal);
			InventoryDecision inventoryDecision = new DurableInventoryParticipant(journal, inventoryImageStore, () => 0.0).Commit(record.Id);
			if (inventoryDecision != InventoryDecision.Committed || !(inventoryImageStore is IInventoryCheckpointStore inventoryCheckpointStore) || inventoryCheckpointStore.ReceiptConfirmed(record.Source))
			{
				return inventoryDecision;
			}
			return InventoryDecision.RecoveryRequired;
		}

		public void Dispose()
		{
			settlements?.Dispose();
			debits?.Dispose();
			outcomes?.Dispose();
			intents?.Dispose();
		}
	}
	public enum HeldWrite
	{
		Run,
		Refuse,
		Defer,
		Neutral,
		Skipped
	}
	public static class TheftHoldRules
	{
		public static readonly string[] Guarded = new string[7] { "AddItem", "RemoveItem", "RemoveAll", "RemoveOneItem", "RemoveItemByName", "MoveItemToThis", "MoveAll" };

		public const int MaxDeferred = 256;

		public static HeldWrite Classify(string method, bool returnsVoid)
		{
			return Classify(method, returnsVoid, insideLoad: false);
		}

		public static HeldWrite Classify(string method, bool returnsVoid, bool insideLoad)
		{
			if (insideLoad || Array.IndexOf(Guarded, method) < 0)
			{
				return HeldWrite.Run;
			}
			if (!returnsVoid)
			{
				return HeldWrite.Refuse;
			}
			if (!(method == "MoveAll") && !(method == "MoveItemToThis"))
			{
				return HeldWrite.Defer;
			}
			return HeldWrite.Neutral;
		}

		public static HeldWrite Decide(string method, bool returnsVoid, bool held, bool runOriginal)
		{
			return Decide(method, returnsVoid, held, runOriginal, insideLoad: false);
		}

		public static HeldWrite Decide(string method, bool returnsVoid, bool held, bool runOriginal, bool insideLoad)
		{
			if (!runOriginal)
			{
				return HeldWrite.Skipped;
			}
			if (!held)
			{
				return HeldWrite.Run;
			}
			return Classify(method, returnsVoid, insideLoad);
		}

		public static bool HoldOnOpen(IEnumerable<bool> settledAndConfirmed)
		{
			return settledAndConfirmed.Any((bool settled) => !settled);
		}
	}
	public sealed class DeferredWrites<TTarget, TCall> where TTarget : class
	{
		private sealed class ReferenceComparer : IEqualityComparer<TTarget>
		{
			internal static readonly ReferenceComparer Instance = new ReferenceComparer();

			public bool Equals(TTarget? x, TTarget? y)
			{
				return x == y;
			}

			public int GetHashCode(TTarget obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private readonly List<(TTarget Target, TCall Call)> entries = new List<(TTarget, TCall)>();

		private readonly int limit;

		public int Count => entries.Count;

		public DeferredWrites(int limit = 256)
		{
			this.limit = limit;
		}

		public bool TryAdd(TTarget target, TCall call)
		{
			if (entries.Count >= limit)
			{
				return false;
			}
			entries.Add((target, call));
			return true;
		}

		public int Drain(Func<TTarget, bool> held, Action<TTarget, TCall> apply)
		{
			if (entries.Count == 0)
			{
				return 0;
			}
			int num = 0;
			HashSet<TTarget> hashSet = new HashSet<TTarget>(ReferenceComparer.Instance);
			int num2 = 0;
			while (num2 < entries.Count)
			{
				var (val, arg) = entries[num2];
				if (hashSet.Contains(val) || held(val))
				{
					hashSet.Add(val);
					num2++;
				}
				else
				{
					entries.RemoveAt(num2);
					num++;
					apply(val, arg);
				}
			}
			return num;
		}
	}
	public sealed class TheftCleanupResult
	{
		public static readonly TheftCleanupResult None = new TheftCleanupResult(Array.Empty<TheftRequest>(), Array.Empty<string>(), 0, 0, null);

		public IReadOnlyList<TheftRequest> Pruned { get; }

		public IReadOnlyList<string> ReceiptRoots { get; }

		public int Rows { get; }

		public int Kept { get; }

		public string? Skipped { get; }

		public string? Error { get; }

		public TheftCleanupResult(IReadOnlyList<TheftRequest> pruned, IReadOnlyList<string> receiptRoots, int rows, int kept, string? skipped, string? error = null)
		{
			Pruned = pruned;
			ReceiptRoots = receiptRoots;
			Rows = rows;
			Kept = kept;
			Skipped = skipped;
			Error = error;
		}
	}
	public static class TheftJournalCleanup
	{
		public const int KeepRecent = 32;

		public const int Trigger = 64;

		public const string Manifest = "cleanup.pending";

		private const string Copy = ".cleanup-new";

		private const string Backup = ".cleanup-old";

		private static readonly string[] Journals = new string[4] { "intent.bin", "outcome.bin", "debit.bin", "settlement.bin" };

		public static string DebitSource(string root)
		{
			return "wildblood.theft.debit." + root;
		}

		public static string SettleSource(string root)
		{
			return "wildblood.theft.settle." + root;
		}

		public static IEnumerable<string> Sources(string root)
		{
			yield return DebitSource(root);
			yield return SettleSource(root);
		}

		public static void Recover(string directory)
		{
			if (!Directory.Exists(directory))
			{
				return;
			}
			string[] journals = Journals;
			foreach (string path in journals)
			{
				string text = Path.Combine(directory, path);
				string text2 = text + ".cleanup-old";
				string path2 = text + ".cleanup-new";
				if (File.Exists(text2))
				{
					if (File.Exists(text))
					{
						File.Delete(text2);
					}
					else
					{
						File.Move(text2, text);
					}
				}
				if (File.Exists(path2))
				{
					File.Delete(path2);
				}
			}
			string path3 = Path.Combine(directory, "cleanup.pending") + ".cleanup-new";
			if (File.Exists(path3))
			{
				File.Delete(path3);
			}
		}

		public static TheftCleanupResult Run(string directory, Func<string, bool> receiptSaved)
		{
			Recover(directory);
			try
			{
				return Prune(directory, receiptSaved, 32, 64, null);
			}
			catch (Exception ex)
			{
				Recover(directory);
				return new TheftCleanupResult(Array.Empty<TheftRequest>(), Array.Empty<string>(), 0, 0, null, ex.GetType().Name);
			}
		}

		public static TheftCleanupResult Prune(string directory, Func<string, bool> receiptSaved, int keep, int trigger, Action<string>? step)
		{
			if (keep < 1 || trigger < keep)
			{
				throw new ArgumentOutOfRangeException("trigger");
			}
			if (!File.Exists(Path.Combine(directory, Journals[0])))
			{
				return TheftCleanupResult.None;
			}
			Dictionary<string, IReadOnlyList<InventoryDecisionRecord>> dictionary = Journals.ToDictionary((string n) => n, (string n) => Read(Path.Combine(directory, n)));
			List<TheftIntent> list = dictionary[Journals[0]].Select((InventoryDecisionRecord r) => new TheftIntent(r)).ToList();
			HashSet<string> present = new HashSet<string>(dictionary.Values.SelectMany((IReadOnlyList<InventoryDecisionRecord> l) => l.Select((InventoryDecisionRecord r) => r.Id)), StringComparer.Ordinal);
			List<string> pending = (from root in ReadManifest(directory)
				where present.Contains(root) || Sources(root).Any(receiptSaved)
				select root).ToList();
			string text = null;
			if (dictionary.Values.SelectMany((IReadOnlyList<InventoryDecisionRecord> l) => l).Any((InventoryDecisionRecord r) => r.State != InventoryDecision.Committed))
			{
				text = "unsettled";
			}
			else
			{
				List<IReadOnlyList<InventoryDecisionRecord>> source = (from p in dictionary
					where p.Key != Journals[0]
					select p.Value).ToList();
				foreach (TheftIntent intent in list)
				{
					if (source.Any((IReadOnlyList<InventoryDecisionRecord> l) => l.All((InventoryDecisionRecord r) => r.Id != intent.Root)) || !Sources(intent.Root).All(receiptSaved))
					{
						text = "unconfirmed";
						break;
					}
				}
			}
			List<TheftIntent> list2 = new List<TheftIntent>();
			HashSet<string> remove = new HashSet<string>(StringComparer.Ordinal);
			if (text == null)
			{
				IEnumerable<TheftIntent> first;
				if (list.Count <= trigger)
				{
					IEnumerable<TheftIntent> enumerable = Enumerable.Empty<TheftIntent>();
					first = enumerable;
				}
				else
				{
					first = list.OrderByDescending((TheftIntent i) => i.Request.Counter).Skip(keep);
				}
				list2 = first.Concat(list.Where((TheftIntent i) => pending.Contains(i.Root))).Distinct().ToList();
				remove.UnionWith(list2.Select((TheftIntent i) => i.Root));
				HashSet<string> known = new HashSet<string>(list.Select((TheftIntent i) => i.Root), StringComparer.Ordinal);
				remove.UnionWith(present.Where((string id) => !known.Contains(id)));
			}
			List<string> list3 = pending.Concat(remove).Distinct<string>(StringComparer.Ordinal).ToList();
			List<string> receiptRoots = list3.Where((string r) => !present.Contains(r) || remove.Contains(r)).ToList();
			WriteManifest(directory, list3);
			if (remove.Count == 0)
			{
				if (list3.Count != 0 || text != null)
				{
					return new TheftCleanupResult(Array.Empty<TheftRequest>(), receiptRoots, 0, list.Count, text);
				}
				return TheftCleanupResult.None;
			}
			step?.Invoke("manifest");
			int num = 0;
			string[] journals = Journals;
			foreach (string text2 in journals)
			{
				num += Compact(directory, text2, dictionary[text2], remove, step);
			}
			return new TheftCleanupResult(list2.Select((TheftIntent i) => i.Request).ToList(), receiptRoots, num, list.Count - list2.Count, null);
		}

		private static IReadOnlyList<InventoryDecisionRecord> Read(string path)
		{
			if (!File.Exists(path))
			{
				return Array.Empty<InventoryDecisionRecord>();
			}
			using InventoryDecisionJournal inventoryDecisionJournal = new InventoryDecisionJournal(path, requireExisting: true);
			return inventoryDecisionJournal.Snapshot();
		}

		private static int Compact(string directory, string name, IReadOnlyList<InventoryDecisionRecord> all, HashSet<string> remove, Action<string>? step)
		{
			List<InventoryDecisionRecord> list = all.Where((InventoryDecisionRecord r) => !remove.Contains(r.Id)).ToList();
			if (list.Count == all.Count)
			{
				return 0;
			}
			string text = Path.Combine(directory, name);
			string text2 = text + ".cleanup-new";
			string text3 = text + ".cleanup-old";
			if (File.Exists(text2))
			{
				File.Delete(text2);
			}
			using (InventoryDecisionJournal inventoryDecisionJournal = new InventoryDecisionJournal(text2))
			{
				foreach (InventoryDecisionRecord item in list)
				{
					InventoryDecisionRecord record = inventoryDecisionJournal.Prepare(item.Id, item.Source, item.Fingerprint, item.Deadline, item.Before, item.After, item.Metadata);
					foreach (InventoryDecision item2 in Steps(item.State))
					{
						inventoryDecisionJournal.Transition(record, item2);
					}
				}
			}
			InventoryDecisionJournal check = new InventoryDecisionJournal(text2, requireExisting: true);
			try
			{
				if (check.Snapshot().Count != list.Count || list.Any((InventoryDecisionRecord r) => !Same(r, check.Find(r.Id))))
				{
					throw new InvalidDataException("Theft journal copy differs from the kept records.");
				}
			}
			finally
			{
				if (check != null)
				{
					((IDisposable)check).Dispose();
				}
			}
			step?.Invoke(name + ".copied");
			File.Replace(text2, text, text3);
			step?.Invoke(name + ".swapped");
			File.Delete(text3);
			return all.Count - list.Count;
		}

		private static IEnumerable<InventoryDecision> Steps(InventoryDecision state)
		{
			switch (state)
			{
			case InventoryDecision.Decided:
				yield return InventoryDecision.Decided;
				break;
			case InventoryDecision.Committed:
				yield return InventoryDecision.Decided;
				yield return InventoryDecision.Committed;
				break;
			case InventoryDecision.Aborted:
				yield return InventoryDecision.Aborted;
				break;
			}
		}

		private static bool Same(InventoryDecisionRecord a, InventoryDecisionRecord? b)
		{
			if (b != null && a.Id == b.Id && a.Source == b.Source && a.Fingerprint == b.Fingerprint && a.Deadline.Equals(b.Deadline) && a.State == b.State && ((ReadOnlySpan<byte>)a.Before).SequenceEqual((ReadOnlySpan<byte>)b.Before) && ((ReadOnlySpan<byte>)a.After).SequenceEqual((ReadOnlySpan<byte>)b.After))
			{
				return ((ReadOnlySpan<byte>)a.Metadata).SequenceEqual((ReadOnlySpan<byte>)b.Metadata);
			}
			return false;
		}

		private static List<string> ReadManifest(string directory)
		{
			string path = Path.Combine(directory, "cleanup.pending");
			if (!File.Exists(path))
			{
				return new List<string>();
			}
			try
			{
				List<string> source = (from l in File.ReadAllLines(path)
					where l.Length != 0
					select l).ToList();
				return source.All(TheftWire.IsRoot) ? source.Distinct<string>(StringComparer.Ordinal).ToList() : new List<string>();
			}
			catch (IOException)
			{
				return new List<string>();
			}
		}

		private static void WriteManifest(string directory, IReadOnlyCollection<string> roots)
		{
			string text = Path.Combine(directory, "cleanup.pending");
			if (roots.Count == 0)
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				return;
			}
			string text2 = text + ".cleanup-new";
			using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				using StreamWriter streamWriter = new StreamWriter(fileStream);
				foreach (string root in roots)
				{
					streamWriter.WriteLine(root);
				}
				streamWriter.Flush();
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(text))
			{
				File.Replace(text2, text, null);
			}
			else
			{
				File.Move(text2, text);
			}
		}
	}
	public sealed class TheftRequest
	{
		public string Root { get; }

		public long World { get; }

		public long Actor { get; }

		public long SourceUser { get; }

		public uint SourceId { get; }

		public long Owner { get; }

		public int Counter { get; }

		public int X { get; }

		public int Y { get; }

		public string Session { get; }

		public string Fingerprint { get; }

		public TheftRequest(string root, long world, long actor, long sourceUser, uint sourceId, long owner, int counter, int x, int y, string session, string fingerprint)
		{
			Root = root;
			World = world;
			Actor = actor;
			SourceUser = sourceUser;
			SourceId = sourceId;
			Owner = owner;
			Counter = counter;
			X = x;
			Y = y;
			Session = session;
			Fingerprint = fingerprint;
		}
	}
	public enum TheftDisposition
	{
		Approved,
		Denied,
		Recovery
	}
	public sealed class TheftReceipt
	{
		public string Root { get; }

		public string Fingerprint { get; }

		public TheftDisposition Disposition { get; }

		public TheftReceipt(string root, string fingerprint, TheftDisposition disposition)
		{
			Root = root;
			Fingerprint = fingerprint;
			Disposition = disposition;
		}

		public bool Matches(TheftRequest request)
		{
			if (request != null && Root == request.Root)
			{
				return Fingerprint == TheftMetadata.Hash(TheftMetadata.Encode(request));
			}
			return false;
		}

		public static TheftReceipt For(TheftRequest request, bool banked)
		{
			return new TheftReceipt(request.Root, TheftMetadata.Hash(TheftMetadata.Encode(request)), (!banked) ? TheftDisposition.Denied : TheftDisposition.Approved);
		}
	}
	public static class TheftMetadata
	{
		public const int Maximum = 2048;

		public static string Hash(byte[] value)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(value)).Replace("-", "").ToLowerInvariant();
		}

		private static bool Hex(string value, int length)
		{
			if (value != null && value.Length == length)
			{
				return value.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
			}
			return false;
		}

		public static byte[] Encode(TheftRequest request)
		{
			if (request == null || !Hex(request.Root, 32) || request.World == 0L || request.Actor == 0L || request.Owner == 0L || request.Counter <= 0 || request.SourceUser == 0L || request.SourceId == 0 || request.X < 0 || request.Y < 0 || request.X >= 256 || request.Y >= 256 || string.IsNullOrEmpty(request.Session) || request.Session.Length > 128 || request.Session.Any(char.IsControl) || !Hex(request.Fingerprint, 64))
			{
				throw new InvalidDataException("Theft correlation invalid.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(1);
				binaryWriter.Write(request.Root);
				binaryWriter.Write(request.World);
				binaryWriter.Write(request.Actor);
				binaryWriter.Write(request.SourceUser);
				binaryWriter.Write(request.SourceId);
				binaryWriter.Write(request.Owner);
				binaryWriter.Write(request.Counter);
				binaryWriter.Write(request.X);
				binaryWriter.Write(request.Y);
				binaryWriter.Write(request.Session);
				binaryWriter.Write(request.Fingerprint);
			}
			return memoryStream.ToArray();
		}

		public static TheftRequest Decode(byte[] value)
		{
			if (value == null || value.Length > 2048)
			{
				throw new InvalidDataException("Theft correlation bound.");
			}
			using MemoryStream memoryStream = new MemoryStream(value, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Theft correlation version.");
			}
			TheftRequest theftRequest = new TheftRequest(binaryReader.ReadString(), binaryReader.ReadInt64(), binaryReader.ReadInt64(), binaryReader.ReadInt64(), binaryReader.ReadUInt32(), binaryReader.ReadInt64(), binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadString(), binaryReader.ReadString());
			if (memoryStream.Position != memoryStream.Length || !((ReadOnlySpan<byte>)Encode(theftRequest)).SequenceEqual((ReadOnlySpan<byte>)value))
			{
				throw new InvalidDataException("Theft correlation trailing data.");
			}
			return theftRequest;
		}

		public static byte[] Encode(TheftReceipt receipt)
		{
			if (receipt == null || !Hex(receipt.Root, 32) || !Hex(receipt.Fingerprint, 64) || !Enum.IsDefined(typeof(TheftDisposition), receipt.Disposition))
			{
				throw new InvalidDataException("Theft outcome invalid.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(1);
				binaryWriter.Write(receipt.Root);
				binaryWriter.Write(receipt.Fingerprint);
				binaryWriter.Write((int)receipt.Disposition);
			}
			return memoryStream.ToArray();
		}

		public static TheftReceipt Receipt(byte[] value)
		{
			if (value == null || value.Length > 2048)
			{
				throw new InvalidDataException("Theft outcome bound.");
			}
			using MemoryStream memoryStream = new MemoryStream(value, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Theft outcome version.");
			}
			TheftReceipt theftReceipt = new TheftReceipt(binaryReader.ReadString(), binaryReader.ReadString(), (TheftDisposition)binaryReader.ReadInt32());
			if (memoryStream.Position != memoryStream.Length || !((ReadOnlySpan<byte>)Encode(theftReceipt)).SequenceEqual((ReadOnlySpan<byte>)value))
			{
				throw new InvalidDataException("Theft outcome trailing data.");
			}
			return theftReceipt;
		}
	}
}
namespace ExtraPotions.Wildblood.Traits
{
	public static class LegacyMasks
	{
		private static readonly (Modifier? Modifier, Mutation? Mutation)[] CreatureManagerBits = new(Modifier?, Mutation?)[32]
		{
			(Modifier.Armored, null),
			(Modifier.Enraged, null),
			(Modifier.Deathward, null),
			(Modifier.Swift, null),
			(Modifier.Regenerating, null),
			(Modifier.Vampiric, null),
			(Modifier.Fire, null),
			(Modifier.Frost, null),
			(Modifier.Lightning, null),
			(Modifier.ToxicDeath, null),
			(Modifier.ArmorPiercing, null),
			(Modifier.Staggering, null),
			(Modifier.AttackSpeed, null),
			(Modifier.Exposed, null),
			(Modifier.Weakened, null),
			(Modifier.Withered, null),
			(Modifier.Reflection, null),
			(Modifier.Vortex, null),
			(Modifier.Crippling, null),
			(Modifier.Disruptive, null),
			(Modifier.Adaptive, null),
			(Modifier.Omen, null),
			(Modifier.Reaping, null),
			(Modifier.Spirit, null),
			(null, Mutation.Blinking),
			(Modifier.Crushing, null),
			(Modifier.Unflinching, null),
			(Modifier.AdrenalineDrain, null),
			(null, Mutation.Corrodent),
			(Modifier.Undodgeable, null),
			(Modifier.Chameleon, null),
			(Modifier.Blamer, null)
		};

		public static long ToCreatureManagerModifiers(IEnumerable<Modifier> modifiers)
		{
			HashSet<Modifier> hashSet = new HashSet<Modifier>(modifiers);
			long num = 0L;
			for (int i = 0; i < CreatureManagerBits.Length; i++)
			{
				Modifier? item = CreatureManagerBits[i].Modifier;
				if (item.HasValue)
				{
					Modifier valueOrDefault = item.GetValueOrDefault();
					if (hashSet.Contains(valueOrDefault))
					{
						num |= 1L << i;
					}
				}
			}
			return num;
		}

		public static TraitSet FromCreatureManager(long mask, Biome biome = Biome.Other)
		{
			List<Modifier> list = new List<Modifier>();
			int num = 0;
			for (int i = 0; i < CreatureManagerBits.Length; i++)
			{
				if ((mask & (1L << i)) != 0L)
				{
					var (modifier, mutation) = CreatureManagerBits[i];
					if (modifier.HasValue)
					{
						list.Add(modifier.Value);
					}
					if (mutation.HasValue)
					{
						num |= Mutations.Bit(mutation.Value);
					}
				}
			}
			return new TraitSet(num, list, biome);
		}
	}
	public enum ModifierGroup
	{
		Offense,
		Defense,
		Affliction,
		Special
	}
	public enum Modifier
	{
		Enraged,
		Fire,
		Frost,
		Lightning,
		Spirit,
		ArmorPiercing,
		Staggering,
		Undodgeable,
		Armored,
		Deathward,
		Regenerating,
		Reflection,
		Vortex,
		Adaptive,
		Unflinching,
		Chameleon,
		Exposed,
		Weakened,
		Withered,
		Crippling,
		Disruptive,
		AdrenalineDrain,
		ToxicDeath,
		Swift,
		AttackSpeed,
		Vampiric,
		Reaping,
		Omen,
		Crushing,
		Blamer
	}
	public enum ModifierCap
	{
		Max4,
		Max3,
		Max2,
		Max1,
		Off
	}
	public static class Modifiers
	{
		public static IReadOnlyList<Modifier> InOrder { get; } = Array.AsReadOnly((Modifier[])Enum.GetValues(typeof(Modifier)));

		public static ModifierGroup GroupOf(Modifier modifier)
		{
			if (modifier <= Modifier.Undodgeable)
			{
				return ModifierGroup.Offense;
			}
			if (modifier <= Modifier.Chameleon)
			{
				return ModifierGroup.Defense;
			}
			if (modifier <= Modifier.ToxicDeath)
			{
				return ModifierGroup.Affliction;
			}
			return ModifierGroup.Special;
		}

		public static IReadOnlyList<Modifier> InGroup(ModifierGroup group)
		{
			return InOrder.Where((Modifier m) => GroupOf(m) == group).ToList();
		}

		public static int Count(ModifierCap cap)
		{
			return cap switch
			{
				ModifierCap.Max4 => 4, 
				ModifierCap.Max3 => 3, 
				ModifierCap.Max2 => 2, 
				ModifierCap.Max1 => 1, 
				_ => 0, 
			};
		}

		public static string Id(Modifier modifier)
		{
			return modifier.ToString().ToLowerInvariant();
		}

		public static bool TryParseId(string? id, out Modifier modifier)
		{
			foreach (Modifier item in InOrder)
			{
				if (string.Equals(Id(item), (id ?? "").Trim(), StringComparison.OrdinalIgnoreCase))
				{
					modifier = item;
					return true;
				}
			}
			modifier = Modifier.Enraged;
			return false;
		}

		public static Modifier? FromCreatureManager(string? name, out Mutation? merged)
		{
			merged = null;
			string text = (name ?? "").Replace(" ", "").Trim();
			if (text.Equals("Blink", StringComparison.OrdinalIgnoreCase))
			{
				merged = Mutation.Blinking;
				return null;
			}
			if (text.Equals("Corrosive", StringComparison.OrdinalIgnoreCase))
			{
				merged = Mutation.Corrodent;
				return null;
			}
			if (text.Equals("Juggernaut", StringComparison.OrdinalIgnoreCase))
			{
				return Modifier.Crushing;
			}
			foreach (Modifier item in InOrder)
			{
				if (item.ToString().Equals(text, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}
	}
	public enum Mutation
	{
		Mad,
		Bloated,
		Cloaked,
		Splintering,
		Leeching,
		Warding,
		Plated,
		Miasmic,
		Devouring,
		Thieving,
		Gilded,
		Blinking,
		Relentless,
		Juggernaut,
		Screecher,
		Frostbound,
		Mudbound,
		Corrodent,
		Cloning,
		Flamebound,
		Piercing,
		Howling,
		Binding
	}
	public static class Mutations
	{
		public static IReadOnlyList<Mutation> InOrder { get; } = Array.AsReadOnly((Mutation[])Enum.GetValues(typeof(Mutation)));

		public static int AllBits => (1 << InOrder.Count) - 1;

		public static int Bit(Mutation mutation)
		{
			return 1 << (int)mutation;
		}

		public static int Mask(IEnumerable<Mutation> mutations)
		{
			return mutations.Aggregate(0, (int mask, Mutation m) => mask | Bit(m));
		}

		public static IReadOnlyList<Mutation> Of(int mask)
		{
			return InOrder.Where((Mutation m) => (mask & Bit(m)) != 0).ToList();
		}

		public static string Id(Mutation mutation)
		{
			return mutation.ToString().ToLowerInvariant();
		}

		public static bool TryParseId(string? id, out Mutation mutation)
		{
			foreach (Mutation item in InOrder)
			{
				if (string.Equals(Id(item), (id ?? "").Trim(), StringComparison.OrdinalIgnoreCase))
				{
					mutation = item;
					return true;
				}
			}
			mutation = Mutation.Mad;
			return false;
		}
	}
	public static class BodySize
	{
		public const float LargeRadius = 1f;

		public const float LargeLength = 3.5f;

		public const float MinHealth = 300f;

		private const float Slack = 0.01f;

		public static int LargeBars => Mutations.Bit(Mutation.Gilded) | Mutations.Bit(Mutation.Relentless);

		public static bool IsLarge(float radius, float length, float baseHealth)
		{
			if (baseHealth >= 300f)
			{
				if (!(radius >= 0.99f))
				{
					return length >= 3.49f;
				}
				return true;
			}
			return false;
		}
	}
	public static class MutationBars
	{
		private static readonly Dictionary<string, int> KindBars = new Dictionary<string, int>(StringComparer.Ordinal)
		{
			["Deathsquito"] = Mutations.Bit(Mutation.Cloaked),
			["Hatchling"] = Mutations.Bit(Mutation.Cloaked),
			["Bat"] = Mutations.Bit(Mutation.Mad) | Mutations.Bit(Mutation.Cloaked)
		};

		public static int Of(string prefab, bool large)
		{
			return (large ? BodySize.LargeBars : 0) | OfKind(prefab);
		}

		public static int OfKind(string? prefab)
		{
			if (prefab == null || !KindBars.TryGetValue(prefab, out var value))
			{
				return 0;
			}
			return value;
		}
	}
	public static class MutationPalette
	{
		private static readonly IReadOnlyDictionary<Mutation, string> Defaults = new Dictionary<Mutation, string>
		{
			[Mutation.Mad] = "#E23030",
			[Mutation.Bloated] = "#8B5A2B",
			[Mutation.Cloaked] = "#4AA6FF",
			[Mutation.Splintering] = "#FFFFFF",
			[Mutation.Leeching] = "#33CC33",
			[Mutation.Warding] = "#101C50",
			[Mutation.Plated] = "#F2C40C",
			[Mutation.Miasmic] = "#1E6B2E",
			[Mutation.Devouring] = "#7A0F0F",
			[Mutation.Thieving] = "#A64BE0",
			[Mutation.Gilded] = "#FFE066",
			[Mutation.Blinking] = "#29E0E0",
			[Mutation.Relentless] = "#FF7F24",
			[Mutation.Juggernaut] = "#6E6E6E",
			[Mutation.Screecher] = "#FF5CC8",
			[Mutation.Frostbound] = "#BFEFFF",
			[Mutation.Mudbound] = "#5E4A1E",
			[Mutation.Corrodent] = "#B7410E",
			[Mutation.Cloning] = "#B4A8FF",
			[Mutation.Flamebound] = "#FF4A1C",
			[Mutation.Piercing] = "#A8B8C8",
			[Mutation.Howling] = "#D4C08A",
			[Mutation.Binding] = "#6B8E23"
		};

		public static string Default { get; } = Format(Defaults);

		public static string DefaultColour(Mutation mutation)
		{
			return Defaults[mutation];
		}

		public static IReadOnlyDictionary<Mutation, string> Parse(string? text)
		{
			Dictionary<Mutation, string> dictionary = new Dictionary<Mutation, string>(Defaults);
			string[] array = (text ?? "").Split(',');
			foreach (string text2 in array)
			{
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string value = text2.Substring(0, num).Trim();
					string text3 = text2.Substring(num + 1).Trim();
					if (Enum.TryParse<Mutation>(value, ignoreCase: true, out var result) && Enum.IsDefined(typeof(Mutation), result) && TryColour(text3, out string canonical))
					{
						dictionary[result] = canonical;
					}
				}
			}
			return dictionary;
		}

		public static string Normalize(string? text)
		{
			return Format(Parse(text));
		}

		public static bool TryColour(string? text, out string canonical)
		{
			canonical = "";
			string text2 = (text ?? "").Trim();
			if (text2.Length != 7 || text2[0] != '#')
			{
				return false;
			}
			if (!int.TryParse(text2.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var _))
			{
				return false;
			}
			canonical = text2.ToUpperInvariant();
			return true;
		}

		private static string Format(IReadOnlyDictionary<Mutation, string> colours)
		{
			return string.Join(", ", Mutations.InOrder.Select((Mutation m) => m.ToString() + "=" + colours[m]));
		}
	}
	public static class SwiftMovement
	{
		public static float Bonus(bool swift, float power)
		{
			if (!swift || float.IsNaN(power))
			{
				return 0f;
			}
			return Math.Clamp(power, 0f, 1f);
		}

		public static bool NeedsReapply(float applied, float current)
		{
			return Math.Abs(applied - current) > 0.0001f;
		}
	}
	public sealed class TraitSet : IEquatable<TraitSet>
	{
		public int Mutations { get; }

		public IReadOnlyList<Modifier> Modifiers { get; }

		public Biome Biome { get; }

		public bool IsPlain
		{
			get
			{
				if (Mutations == 0)
				{
					return Modifiers.Count == 0;
				}
				return false;
			}
		}

		public TraitSet(int mutations, IEnumerable<Modifier> modifiers, Biome biome)
		{
			Mutations = mutations & ExtraPotions.Wildblood.Traits.Mutations.AllBits;
			Modifiers = (from m in (modifiers ?? throw new ArgumentNullException("modifiers")).Distinct()
				orderby m
				select m).ToList();
			Biome = biome;
		}

		public static TraitSet Plain(Biome biome)
		{
			return new TraitSet(0, Array.Empty<Modifier>(), biome);
		}

		public bool Equals(TraitSet? other)
		{
			if (other != null && Mutations == other.Mutations && Biome == other.Biome)
			{
				return Modifiers.SequenceEqual(other.Modifiers);
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as TraitSet);
		}

		public override int GetHashCode()
		{
			return (Mutations, Biome, Modifiers.Count).GetHashCode();
		}

		public override string ToString()
		{
			return TraitBlob.Encode(this);
		}
	}
	public static class TraitBlob
	{
		public const string Version = "wb1";

		public const int MaxLength = 256;

		public const string LegacyPrefix = "ecr:";

		public static string Encode(TraitSet traits)
		{
			if (traits == null)
			{
				throw new ArgumentNullException("traits");
			}
			if (traits.IsPlain)
			{
				return "";
			}
			string text = "wb1;m=" + string.Join(",", Mutations.Of(traits.Mutations).Select(Mutations.Id)) + ";c=" + string.Join(",", traits.Modifiers.Select(Modifiers.Id)) + ";b=" + BiomeId(traits.Biome);
			if (text.Length > 256)
			{
				throw new ArgumentException("Trait blob longer than " + 256 + " characters.", "traits");
			}
			return text;
		}

		public static bool TryDecode(string? blob, out TraitSet traits)
		{
			traits = TraitSet.Plain(Biome.Other);
			if (string.IsNullOrEmpty(blob))
			{
				return true;
			}
			if (blob.Length > 256)
			{
				return false;
			}
			if (blob.StartsWith("ecr:", StringComparison.Ordinal))
			{
				if (!int.TryParse(blob.Substring("ecr:".Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0)
				{
					return false;
				}
				traits = new TraitSet(result, Array.Empty<Modifier>(), Biome.Other);
				return true;
			}
			string[] array = blob.Split(';');
			if (array[0] != "wb1")
			{
				return false;
			}
			int num = 0;
			List<Modifier> list = new List<Modifier>();
			Biome biome = Biome.Other;
			foreach (string item in array.Skip(1))
			{
				int num2 = item.IndexOf('=');
				if (num2 < 0)
				{
					continue;
				}
				string text = item.Substring(0, num2);
				string[] array2 = item.Substring(num2 + 1).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				switch (text)
				{
				case "m":
				{
					string[] array3 = array2;
					for (int i = 0; i < array3.Length; i++)
					{
						if (Mutations.TryParseId(array3[i], out var mutation))
						{
							num |= Mutations.Bit(mutation);
						}
					}
					break;
				}
				case "c":
				{
					string[] array3 = array2;
					for (int i = 0; i < array3.Length; i++)
					{
						if (Modifiers.TryParseId(array3[i], out var modifier))
						{
							list.Add(modifier);
						}
					}
					break;
				}
				case "b":
					biome = ParseBiome(array2.FirstOrDefault());
					break;
				}
			}
			traits = new TraitSet(num, list, biome);
			return true;
		}

		public static string BiomeId(Biome biome)
		{
			return biome.ToString().ToLowerInvariant();
		}

		public static Biome ParseBiome(string? id)
		{
			foreach (Biome value in Enum.GetValues(typeof(Biome)))
			{
				if (string.Equals(BiomeId(value), (id ?? "").Trim(), StringComparison.OrdinalIgnoreCase))
				{
					return value;
				}
			}
			return Biome.Other;
		}

		public static string EggEncode(string blob)
		{
			StringBuilder stringBuilder = new StringBuilder();
			byte[] bytes = Encoding.UTF8.GetBytes(blob ?? "");
			for (int i = 0; i < bytes.Length; i++)
			{
				byte b = bytes[i];
				char c = (char)b;
				if (b < 128 && (char.IsLetterOrDigit(c) || c == '.' || c == '_' || c == '-'))
				{
					stringBuilder.Append(c);
				}
				else
				{
					stringBuilder.Append('%').Append(b.ToString("X2", CultureInfo.InvariantCulture));
				}
			}
			return stringBuilder.ToString();
		}

		public static string EggDecode(string egg)
		{
			List<byte> list = new List<byte>();
			string text = egg ?? "";
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] != '%')
				{
					list.Add((byte)text[i]);
					continue;
				}
				if (i + 2 >= text.Length || !byte.TryParse(text.Substring(i + 1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
				{
					return "";
				}
				list.Add(result);
				i += 2;
			}
			return Encoding.UTF8.GetString(list.ToArray());
		}
	}
	public static class TraitInheritance
	{
		public static TraitSet Inherit(TraitSet mother, TraitSet? sire, float mutationChance, int enabled, int barred, Func<Modifier, bool> modifierAllowed, IDice dice, bool mutationsKept = true)
		{
			if (mother == null)
			{
				throw new ArgumentNullException("mother");
			}
			if (modifierAllowed == null)
			{
				throw new ArgumentNullException("modifierAllowed");
			}
			int mask = (mother.Mutations | (sire?.Mutations ?? 0)) & ~barred;
			List<Mutation> list = new List<Mutation>();
			foreach (Mutation item in Mutations.Of(mask))
			{
				if ((enabled & Mutations.Bit(item)) != 0)
				{
					list.Add(item);
				}
			}
			int mutations = 0;
			if (mutationsKept && list.Count > 0 && dice.Percent(mutationChance))
			{
				mutations = Mutations.Bit(list[dice.Range(0, list.Count)]);
			}
			return new TraitSet(mutations, mother.Modifiers.Where(modifierAllowed), mother.Biome);
		}

		public static string Inherit(string mother, string sire, float mutationChance, int enabled, int barred, Func<Modifier, bool> modifierAllowed, IDice dice, bool mutationsKept = true)
		{
			TraitBlob.TryDecode(mother, out TraitSet traits);
			TraitBlob.TryDecode(sire, out TraitSet traits2);
			TraitSet traitSet = Inherit(traits, traits2, mutationChance, enabled, barred, modifierAllowed, dice, mutationsKept);
			if (traitSet.Mutations != 0 || traitSet.Modifiers.Count != 0)
			{
				return TraitBlob.Encode(traitSet);
			}
			return "";
		}
	}
	public static class TraitOverlaps
	{
		public static IReadOnlyList<(Mutation Mutation, Modifier Modifier)> Pairs { get; } = new(Mutation, Modifier)[9]
		{
			(Mutation.Leeching, Modifier.Vampiric),
			(Mutation.Warding, Modifier.Reflection),
			(Mutation.Plated, Modifier.Armored),
			(Mutation.Mad, Modifier.Swift),
			(Mutation.Mad, Modifier.AttackSpeed),
			(Mutation.Juggernaut, Modifier.Unflinching),
			(Mutation.Flamebound, Modifier.Fire),
			(Mutation.Frostbound, Modifier.Frost),
			(Mutation.Juggernaut, Modifier.Crushing)
		};

		public static bool Excludes(int mutations, Modifier modifier)
		{
			foreach (var pair in Pairs)
			{
				var (mutation, _) = pair;
				if (pair.Modifier == modifier && (mutations & Mutations.Bit(mutation)) != 0)
				{
					return true;
				}
			}
			return false;
		}

		public static IReadOnlyList<Modifier> Without(int mutations, IEnumerable<Modifier> modifiers)
		{
			return modifiers.Where((Modifier m) => !Excludes(mutations, m)).ToList();
		}
	}
	public sealed class BiomeTraitRules
	{
		public float[] MutationChance { get; }

		public Dictionary<Mutation, float[]> MutationChances { get; } = new Dictionary<Mutation, float[]>();

		public BiomeTraitRules(float[] mutationChance)
		{
			MutationChance = mutationChance ?? throw new ArgumentNullException("mutationChance");
		}

		public static BiomeTraitRules Default()
		{
			BiomeTraitRules biomeTraitRules = new BiomeTraitRules(new float[6] { 2.5f, 3.5f, 5f, 6f, 7.5f, 10f });
			biomeTraitRules.MutationChances[Mutation.Devouring] = new float[6] { 0.6f, 0.9f, 1.2f, 1.5f, 1.8f, 2.4f };
			biomeTraitRules.MutationChances[Mutation.Gilded] = new float[6] { 0.2f, 0.25f, 0.35f, 0.45f, 0.55f, 0.7f };
			return biomeTraitRules;
		}

		public float ChanceOf(Mutation mutation, int stars)
		{
			float[] value;
			float[] array = (MutationChances.TryGetValue(mutation, out value) ? value : MutationChance);
			if (array.Length == 0)
			{
				return 0f;
			}
			return array[Math.Min(array.Length - 1, Math.Max(0, stars))];
		}
	}
	public static class TraitRoller
	{
		public static int RollMutationsCustom(BiomeTraitRules rules, int stars, int enabled, int barred, float boost, int maxMutations, IDice dice)
		{
			List<Mutation> list = new List<Mutation>();
			foreach (Mutation item in Mutations.InOrder)
			{
				float chancePercent = Math.Min(100f, rules.ChanceOf(item, stars) * Math.Max(0f, boost));
				if (MayRoll(item, enabled, barred) && dice.Percent(chancePercent))
				{
					list.Add(item);
				}
			}
			if (maxMutations > 0 && list.Count > maxMutations)
			{
				for (int num = list.Count - 1; num > 0; num--)
				{
					int num2 = dice.Range(0, num + 1);
					List<Mutation> list2 = list;
					int index = num;
					int index2 = num2;
					Mutation value = list[num2];
					Mutation value2 = list[num];
					list2[index] = value;
					list[index2] = value2;
				}
				list.RemoveRange(maxMutations, list.Count - maxMutations);
			}
			return Mutations.Mask(list);
		}

		public static float PresetRate(float cellRate, int stars)
		{
			return Math.Min(100f, cellRate * (1f + 0.25f * (float)Math.Max(0, stars)));
		}

		public static int RollMutationPreset(float cellRate, int stars, BiomeTraitRules rules, int enabled, int barred, IDice dice)
		{
			if (!dice.Percent(PresetRate(cellRate, stars)))
			{
				return 0;
			}
			IReadOnlyList<Mutation> inOrder = Mutations.InOrder;
			float[] array = new float[inOrder.Count];
			float num = 0f;
			for (int i = 0; i < inOrder.Count; i++)
			{
				array[i] = (MayRoll(inOrder[i], enabled, barred) ? Math.Max(0f, rules.ChanceOf(inOrder[i], stars)) : 0f);
				num += array[i];
			}
			if (num <= 0f)
			{
				return 0;
			}
			return Mutations.Bit(inOrder[dice.PickWeighted(array)]);
		}

		public static bool MayRoll(Mutation mutation, int enabled, int barred)
		{
			if ((enabled & Mutations.Bit(mutation)) != 0)
			{
				return (barred & Mutations.Bit(mutation)) == 0;
			}
			return false;
		}

		public static float DefaultModifierChance(bool boss)
		{
			if (!boss)
			{
				return 5f;
			}
			return 10f;
		}

		public static IReadOnlyList<Modifier> RollModifiers(IReadOnlyDictionary<Modifier, float> chances, ModifierCap cap, IDice dice)
		{
			int num = Modifiers.Count(cap);
			if (num == 0)
			{
				return Array.Empty<Modifier>();
			}
			List<Modifier> list = new List<Modifier>();
			foreach (ModifierGroup value2 in Enum.GetValues(typeof(ModifierGroup)))
			{
				foreach (Modifier item in Modifiers.InGroup(value2))
				{
					if (chances.TryGetValue(item, out var value) && dice.Percent(value))
					{
						list.Add(item);
						break;
					}
				}
			}
			while (list.Count > num)
			{
				list.RemoveAt(dice.Range(0, list.Count));
			}
			return list.OrderBy((Modifier m) => m).ToList();
		}
	}
}
namespace ExtraPotions.Wildblood.Stars
{
	public enum Difficulty
	{
		Easy,
		Medium,
		Hard,
		VeryHard,
		Extreme,
		Custom
	}
	public enum StarMode
	{
		Wildblood,
		KeepExisting,
		Off
	}
	public static class DifficultyNames
	{
		public const string Choices = "Easy, Medium, Hard, Very Hard, Extreme or Custom";

		public static bool TryParse(string? text, out Difficulty difficulty)
		{
			switch ((text ?? "").Replace(" ", "").Replace("_", "").Replace("-", "")
				.ToLowerInvariant())
			{
			case "custom":
				difficulty = Difficulty.Custom;
				return true;
			case "easy":
				difficulty = Difficulty.Easy;
				return true;
			case "medium":
				difficulty = Difficulty.Medium;
				return true;
			case "normal":
				difficulty = Difficulty.Medium;
				return true;
			case "hard":
				difficulty = Difficulty.Hard;
				return true;
			case "veryhard":
				difficulty = Difficulty.VeryHard;
				return true;
			case "extreme":
				difficulty = Difficulty.Extreme;
				return true;
			default:
				difficulty = Difficulty.Medium;
				return false;
			}
		}

		public static string Name(Difficulty difficulty)
		{
			if (difficulty != Difficulty.VeryHard)
			{
				return difficulty.ToString();
			}
			return "Very Hard";
		}
	}
	public static class DistanceScaling
	{
		public const int Damage = 0;

		public const int Health = 1;

		public static readonly float[] DefaultBand = new float[4] { 0.03f, 0.08f, 1000f, 5f };

		public const string DefaultBandText = "[0.03, 0.08, 1000, 5]";

		private const float DefaultStepMeters = 1000f;

		public static float Multiplier(float distance, IReadOnlyList<float>? scaling, int valueIndex)
		{
			if (scaling == null || valueIndex < 0 || scaling.Count <= valueIndex)
			{
				return 1f;
			}
			float num = Math.Max(0f, scaling[valueIndex]);
			if (num <= 0f || float.IsNaN(num) || float.IsInfinity(num))
			{
				return 1f;
			}
			if (float.IsNaN(distance) || float.IsInfinity(distance) || distance <= 0f)
			{
				return 1f;
			}
			float num2 = ((scaling.Count >= 3 && scaling[2] > 0f) ? scaling[2] : 1000f);
			int num3 = (int)Math.Floor(distance / num2);
			if (scaling.Count >= 4 && scaling[3] > 0f)
			{
				num3 = Math.Min(num3, (int)Math.Floor(scaling[3]));
			}
			return Math.Max(0f, 1f + (float)num3 * num);
		}

		public static float Horizontal(float x, float z)
		{
			return (float)Math.Sqrt(x * x + z * z);
		}

		public static bool Active(bool enabled, SpawnSource source)
		{
			if (!enabled)
			{
				return false;
			}
			if ((uint)(source - 6) <= 4u)
			{
				return false;
			}
			return true;
		}
	}
	public static class TamedTraits
	{
		public static bool Stripped(bool keepTraits, bool tamed)
		{
			return !keepTraits && tamed;
		}
	}
	public readonly struct PlayerScalingValues
	{
		public float HealthPerPlayer { get; }

		public float DamagePerPlayer { get; }

		public int MaxPlayers { get; }

		public PlayerScalingValues(float healthPerPlayer, float damagePerPlayer, int maxPlayers)
		{
			HealthPerPlayer = healthPerPlayer;
			DamagePerPlayer = damagePerPlayer;
			MaxPlayers = maxPlayers;
		}
	}
	public static class PlayerScaling
	{
		public static PlayerScalingValues Resolve(float healthPercent, float damagePercent, int maxPlayers)
		{
			return new PlayerScalingValues(Fraction(healthPercent), Fraction(damagePercent), Math.Min(25, Math.Max(1, maxPlayers)));
		}

		private static float Fraction(float percent)
		{
			if (!float.IsNaN(percent) && !float.IsInfinity(percent))
			{
				return Math.Min(200f, Math.Max(0f, percent)) / 100f;
			}
			return 0f;
		}

		public static bool Acts(bool pressureModuleOn, bool gameExists)
		{
			return pressureModuleOn && gameExists;
		}

		public static bool Same(PlayerScalingValues a, PlayerScalingValues b)
		{
			if (a.HealthPerPlayer.Equals(b.HealthPerPlayer) && a.DamagePerPlayer.Equals(b.DamagePerPlayer))
			{
				return a.MaxPlayers == b.MaxPlayers;
			}
			return false;
		}
	}
	public static class SaddleScaling
	{
		public static bool Applies(bool scaleSaddleable, bool isSaddleable)
		{
			if (!scaleSaddleable)
			{
				return !isSaddleable;
			}
			return true;
		}
	}
	public enum PlayerScalingYieldReason
	{
		None,
		NamedMod,
		ExternalWrite
	}
	public static class PlayerScalingOwners
	{
		public static IReadOnlyList<ModuleSource> Builtin { get; } = Array.AsReadOnly(new ModuleSource[1] { ModuleStepAside.CreatureManager });

		public static IReadOnlyList<ModuleSource> Installed(IEnumerable<string> installedGuids, string? configured)
		{
			if (installedGuids == null)
			{
				throw new ArgumentNullException("installedGuids");
			}
			HashSet<string> hashSet = new HashSet<string>(installedGuids.Where((string g) => g != null), StringComparer.Ordinal);
			List<ModuleSource> list = new List<ModuleSource>();
			foreach (ModuleSource item in Builtin)
			{
				if (hashSet.Contains(item.Guid))
				{
					list.Add(item);
				}
			}
			foreach (string guid in NameList.Parse(configured))
			{
				if (hashSet.Contains(guid) && list.All((ModuleSource f) => f.Guid != guid))
				{
					list.Add(new ModuleSource(guid, guid));
				}
			}
			return list;
		}
	}
	public sealed class PlayerScalingOwnersCache
	{
		private IReadOnlyList<ModuleSource>? owners;

		private string? configured;

		private int count = -1;

		public IReadOnlyList<ModuleSource> Installed(ICollection<string> installedGuids, string? configuredList)
		{
			if (installedGuids == null)
			{
				throw new ArgumentNullException("installedGuids");
			}
			if (owners == null || count != installedGuids.Count || !string.Equals(configured, configuredList, StringComparison.Ordinal))
			{
				owners = PlayerScalingOwners.Installed(installedGuids, configuredList);
				configured = configuredList;
				count = installedGuids.Count;
			}
			return owners;
		}

		public void Reset()
		{
			owners = null;
			configured = null;
			count = -1;
		}
	}
	public readonly struct PlayerScalingStep
	{
		public bool Write { get; }

		public PlayerScalingValues Values { get; }

		public PlayerScalingYieldReason StartedYield { get; }

		internal static PlayerScalingStep Nothing => new PlayerScalingStep(write: false, default(PlayerScalingValues), PlayerScalingYieldReason.None);

		public PlayerScalingStep(bool write, PlayerScalingValues values, PlayerScalingYieldReason startedYield)
		{
			Write = write;
			Values = values;
			StartedYield = startedYield;
		}
	}
	public sealed class PlayerScalingCoordinator
	{
		private bool applied;

		private PlayerScalingValues baseline;

		private PlayerScalingValues last;

		public PlayerScalingYieldReason Yielding { get; private set; }

		public bool Applied => applied;

		public PlayerScalingStep Step(bool moduleOn, bool namedOwnerInstalled, PlayerScalingValues current, PlayerScalingValues desired)
		{
			if (!moduleOn)
			{
				Yielding = PlayerScalingYieldReason.None;
				return TakeRestore(current);
			}
			if (namedOwnerInstalled)
			{
				PlayerScalingStep step = TakeRestore(current);
				return Yield(PlayerScalingYieldReason.NamedMod, step);
			}
			if (Yielding == PlayerScalingYieldReason.ExternalWrite)
			{
				return PlayerScalingStep.Nothing;
			}
			Yielding = PlayerScalingYieldReason.None;
			if (applied && !PlayerScaling.Same(current, last))
			{
				applied = false;
				return Yield(PlayerScalingYieldReason.ExternalWrite, PlayerScalingStep.Nothing);
			}
			if (!applied)
			{
				baseline = current;
				applied = true;
			}
			last = desired;
			return new PlayerScalingStep(write: true, desired, PlayerScalingYieldReason.None);
		}

		public PlayerScalingStep Restore(PlayerScalingValues current)
		{
			return TakeRestore(current);
		}

		public void Reset()
		{
			applied = false;
			Yielding = PlayerScalingYieldReason.None;
		}

		private PlayerScalingStep TakeRest