Decompiled source of ForgeOrdnance v1.0.0

ForgeOrdnance.dll

Decompiled 2 days ago
using System;
using System.Buffers.Binary;
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.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using AIGraph;
using Agents;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using CellMenu;
using ForgeAudio;
using ForgeCore;
using ForgeUI;
using ForgeVisual;
using GTFO.API;
using GameData;
using Gear;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using LevelGeneration;
using MTFO.API;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using UnityEngine;
using UnityEngine.AI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ForgeOrdnance")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+66ec6b43e1a1603bac84a4b9a226674300b2fc7d")]
[assembly: AssemblyProduct("ForgeOrdnance")]
[assembly: AssemblyTitle("ForgeOrdnance")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
	[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 ForgeOrdnance
{
	public enum OrdnanceArrivalMode : byte
	{
		Drop,
		Direct
	}
	internal enum ArrivalStage
	{
		Vehicle,
		Contents
	}
	internal static class ArrivalPlan
	{
		private static readonly ArrivalStage[] Dropped = new ArrivalStage[2]
		{
			ArrivalStage.Vehicle,
			ArrivalStage.Contents
		};

		private static readonly ArrivalStage[] Placed = new ArrivalStage[1] { ArrivalStage.Contents };

		internal static IReadOnlyList<ArrivalStage> Stages(OrdnanceArrivalMode mode)
		{
			if (mode != OrdnanceArrivalMode.Direct)
			{
				return Dropped;
			}
			return Placed;
		}

		internal static bool HasVehicle(OrdnanceArrivalMode mode)
		{
			return mode != OrdnanceArrivalMode.Direct;
		}

		internal static float ContentsAfter(OrdnanceArrivalMode mode, float vehicleSeconds)
		{
			if (!HasVehicle(mode))
			{
				return 0f;
			}
			return vehicleSeconds;
		}

		internal static bool TryParse(string? text, out OrdnanceArrivalMode mode)
		{
			if (!(text == "Drop"))
			{
				if (text == "Direct")
				{
					mode = OrdnanceArrivalMode.Direct;
					return true;
				}
				mode = OrdnanceArrivalMode.Drop;
				return false;
			}
			mode = OrdnanceArrivalMode.Drop;
			return true;
		}
	}
	internal readonly struct BeaconFrame
	{
		internal float Grow { get; init; }

		internal float Urgency { get; init; }

		internal float Flash { get; init; }

		internal float HotToWhite { get; init; }

		internal float Width { get; init; }

		internal float GlowWidthScale { get; init; }

		internal float FlareScale { get; init; }

		internal float CoreLevel { get; init; }

		internal float GlowLevel { get; init; }

		internal float FlareLevel { get; init; }

		internal float MarkOpacity { get; init; }

		internal float MarkRemaining { get; init; }

		internal float PingRate { get; init; }
	}
	internal static class BeaconShow
	{
		internal const float FadeIn = 0.3f;

		internal const float RiseSeconds = 0.3f;

		internal const float UrgentSeconds = 3f;

		internal const float FlashSeconds = 0.15f;

		internal const float CalmPingRate = 0.62f;

		internal const float UrgentPingFrom = 1.1f;

		internal const float UrgentPingTo = 3f;

		internal static float Rise(float age, bool fresh)
		{
			if (!fresh)
			{
				return 1f;
			}
			float num = Clamp01(age / 0.3f);
			return 1f - (1f - num) * (1f - num);
		}

		internal static BeaconFrame At(float age, float remaining, float total)
		{
			float num = Clamp01(age / 0.3f);
			float num2 = 0.5f + 0.5f * MathF.Sin(age * 2.6f);
			float num3 = Clamp01(1f - remaining / 3f);
			num3 = num3 * num3 * (3f - 2f * num3);
			float num4 = ((remaining < 0.15f) ? (1f - remaining / 0.15f) : 0f);
			return new BeaconFrame
			{
				Grow = num,
				Urgency = num3,
				Flash = num4,
				HotToWhite = 0.4f * num3 + num4,
				Width = num * (1f + 3f * num4),
				GlowWidthScale = (0.94f + 0.12f * num2) * (1f - 0.25f * num3) * (1f + 2f * num4) / (1f + 3f * num4),
				FlareScale = num * (0.92f + 0.16f * num2) * (1f + 0.4f * num3) * (1f + 2f * num4),
				CoreLevel = MathF.Min(1.6f, (0.85f + 0.15f * num2) * num * (1f + 0.35f * num3) + num4),
				GlowLevel = (0.4f + 0.1f * num2) * num * (1f + 0.4f * num3) + 0.6f * num4,
				FlareLevel = 0.6f * num + num4,
				MarkOpacity = num * (1f + num4),
				MarkRemaining = Clamp01(remaining / total),
				PingRate = ((remaining > 3f) ? 0.62f : (1.1f + 1.9f * num3))
			};
		}

		private static float Clamp01(float v)
		{
			if (!(v < 0f))
			{
				if (!(v > 1f))
				{
					return v;
				}
				return 1f;
			}
			return 0f;
		}
	}
	public sealed class OrdnanceCategoryInfo
	{
		public string Id { get; }

		public int Order { get; }

		public uint BeamColour { get; }

		internal Func<string> NameOf { get; }

		public string Name => NameOf();

		internal OrdnanceCategoryInfo(string id, Func<string> name, int order, uint beamColour)
		{
			Id = id;
			NameOf = name;
			Order = order;
			BeamColour = beamColour;
		}
	}
	internal sealed class CategoryRegistry
	{
		public const string Supply = "Supply";

		public const string Defensive = "Defensive";

		public const string Weapon = "Weapon";

		public const string Offensive = "Offensive";

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

		public const uint UnknownBeamColour = 16738847u;

		public IEnumerable<OrdnanceCategoryInfo> Ordered => _all.Values.OrderBy((OrdnanceCategoryInfo c) => c.Order).ThenBy<OrdnanceCategoryInfo, string>((OrdnanceCategoryInfo c) => c.Id, StringComparer.Ordinal);

		public CategoryRegistry(Func<string, Func<string>> builtinName)
		{
			Register("Supply", builtinName("Supply"), 0, 4172287u);
			Register("Defensive", builtinName("Defensive"), 1, 8191882u);
			Register("Weapon", builtinName("Weapon"), 2, 16777215u);
			Register("Offensive", builtinName("Offensive"), 4, 16738847u);
		}

		public void Register(string id, Func<string> name, int order, uint beamColour)
		{
			if (string.IsNullOrEmpty(id) || !id.All(char.IsLetterOrDigit))
			{
				throw new ArgumentException("A category id is a plain name of letters and digits.", "id");
			}
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}
			if (!_all.TryAdd(id, new OrdnanceCategoryInfo(id, name, order, beamColour & 0xFFFFFF)))
			{
				throw new InvalidOperationException("Category '" + id + "' is already registered.");
			}
		}

		public bool TryGet(string id, out OrdnanceCategoryInfo info)
		{
			return _all.TryGetValue(id, out info);
		}

		public uint BeamColour(string id)
		{
			if (!_all.TryGetValue(id, out OrdnanceCategoryInfo value))
			{
				return 16738847u;
			}
			return value.BeamColour;
		}
	}
	internal sealed class ClaimBook
	{
		private readonly Dictionary<(ulong Summoner, uint ThrowId), (ulong Player, uint Attempt)> _claims = new Dictionary<(ulong, uint), (ulong, uint)>();

		private readonly HashSet<(ulong, uint)> _open = new HashSet<(ulong, uint)>();

		public void Open(ulong summoner, uint throwId)
		{
			_open.Add((summoner, throwId));
		}

		public bool IsOpen(ulong summoner, uint throwId)
		{
			return _open.Contains((summoner, throwId));
		}

		public bool TryClaim(ulong summoner, uint throwId, ulong player, uint attempt)
		{
			(ulong, uint) tuple = (summoner, throwId);
			if (attempt == 0 || !_open.Contains(tuple) || _claims.ContainsKey(tuple))
			{
				return false;
			}
			foreach (var value in _claims.Values)
			{
				if (value.Player == player)
				{
					return false;
				}
			}
			_claims[tuple] = (player, attempt);
			return true;
		}

		public bool Matches(ulong summoner, uint throwId, ulong player, uint attempt)
		{
			if (_claims.TryGetValue((summoner, throwId), out (ulong, uint) value))
			{
				(ulong, uint) tuple = value;
				if (tuple.Item1 == player)
				{
					return tuple.Item2 == attempt;
				}
				return false;
			}
			return false;
		}

		public bool Confirm(ulong summoner, uint throwId, ulong player, uint attempt)
		{
			if (!Matches(summoner, throwId, player, attempt) || !_open.Remove((summoner, throwId)))
			{
				return false;
			}
			_claims.Remove((summoner, throwId));
			return true;
		}

		public bool Cancel(ulong summoner, uint throwId, ulong player, uint attempt)
		{
			if (_open.Contains((summoner, throwId)) && Matches(summoner, throwId, player, attempt))
			{
				return _claims.Remove((summoner, throwId));
			}
			return false;
		}

		public bool IsReserved(ulong summoner, uint throwId)
		{
			return _claims.ContainsKey((summoner, throwId));
		}

		public void Close(ulong summoner, uint throwId)
		{
			_open.Remove((summoner, throwId));
			_claims.Remove((summoner, throwId));
		}

		public void Clear()
		{
			_claims.Clear();
			_open.Clear();
		}
	}
	public enum Direction : byte
	{
		Up = 1,
		Down,
		Left,
		Right
	}
	public sealed class OrdnanceDefinition
	{
		public const int UnlimitedUses = -1;

		public IReadOnlyList<OrdnanceStat> Stats { get; }

		public string Id { get; }

		public string Name { get; }

		public bool Callable { get; }

		public string Category { get; }

		public uint CategoryBeamRgb { get; }

		public string Icon { get; }

		public string Description { get; }

		public IReadOnlyList<Direction> Code { get; }

		public double Cooldown { get; }

		public int Uses { get; }

		public bool Unlimited => Uses == -1;

		public double CallInDelay { get; }

		public string PayloadType { get; }

		public JsonElement Payload { get; }

		public object Parameters { get; }

		public string SourceFile { get; }

		public uint Key { get; }

		public uint? BeamColour { get; }

		public uint BeamRgb => BeamColour ?? CategoryBeamRgb;

		internal OrdnanceDefinition(string id, string name, string category, uint categoryBeam, string icon, string description, IReadOnlyList<Direction> code, double cooldown, int uses, double callInDelay, string payloadType, JsonElement payload, object parameters, string sourceFile, uint? beamColour = null, IReadOnlyList<OrdnanceStat>? stats = null, bool callable = true)
		{
			Id = id;
			Name = name;
			Category = category;
			CategoryBeamRgb = categoryBeam;
			Icon = icon;
			Description = description;
			Code = code;
			Cooldown = cooldown;
			Uses = uses;
			CallInDelay = callInDelay;
			PayloadType = payloadType;
			Payload = payload;
			Parameters = parameters;
			SourceFile = sourceFile;
			Key = Fnv.Hash32(id);
			BeamColour = beamColour;
			Stats = stats ?? Array.Empty<OrdnanceStat>();
			Callable = callable;
		}
	}
	public sealed class OrdnanceStat
	{
		public string Name { get; }

		public int Value { get; }

		public int Max { get; }

		internal OrdnanceStat(string name, int value, int max)
		{
			Name = name;
			Value = value;
			Max = max;
		}
	}
	public interface IOrdnancePayloadParser
	{
		object Parse(JsonElement payload);
	}
	public sealed class PayloadParameterException : StrictJsonException
	{
		public PayloadParameterException(string field, string message)
			: base(field, message)
		{
		}
	}
	internal sealed class PayloadRegistry
	{
		private readonly Dictionary<string, IOrdnancePayloadParser> _parsers = new Dictionary<string, IOrdnancePayloadParser>(StringComparer.Ordinal);

		public IEnumerable<string> Types => _parsers.Keys;

		public ulong TypesHash => Hashing.TypesHash(_parsers.Keys);

		public void Register(string type, IOrdnancePayloadParser parser)
		{
			if (string.IsNullOrWhiteSpace(type) || type.Trim() != type)
			{
				throw new ArgumentException("Payload type must be a non-empty name without surrounding spaces.", "type");
			}
			if (parser == null)
			{
				throw new ArgumentNullException("parser");
			}
			if (!_parsers.TryAdd(type, parser))
			{
				throw new InvalidOperationException("Payload type '" + type + "' is already registered.");
			}
		}

		public bool TryGet(string type, out IOrdnancePayloadParser parser)
		{
			return _parsers.TryGetValue(type, out parser);
		}
	}
	internal sealed class ParseResult
	{
		public List<OrdnanceDefinition> Definitions { get; } = new List<OrdnanceDefinition>();

		public List<string> Errors { get; } = new List<string>();
	}
	internal static class DefinitionParser
	{
		private sealed class FieldException : Exception
		{
			internal string Field { get; }

			internal FieldException(string field, string message)
				: base(message)
			{
				Field = field;
			}
		}

		public const int MaxCodeLength = 8;

		public const int MaxIdLength = 64;

		public const double MaxCooldown = 3600.0;

		public const int MaxUses = 99;

		public const double MaxCallInDelay = 120.0;

		public const int MaxDescriptionLength = 400;

		public const int MaxStats = 6;

		public const int MaxStatNameLength = 16;

		public const int MaxStatBoxes = 10;

		public const int DefaultStatBoxes = 5;

		private static readonly JsonDocumentOptions Options = new JsonDocumentOptions
		{
			CommentHandling = JsonCommentHandling.Skip,
			AllowTrailingCommas = true
		};

		private static readonly string[] Fields = new string[13]
		{
			"Id", "Name", "Callable", "Category", "Icon", "Description", "Stats", "Code", "Cooldown", "Uses",
			"CallInDelay", "BeamColour", "Payload"
		};

		private static readonly string[] CallerFields = new string[7] { "Category", "Code", "Cooldown", "Uses", "CallInDelay", "Stats", "BeamColour" };

		private static readonly string[] StatFields = new string[3] { "Name", "Value", "Max" };

		public static ParseResult ParseFile(string fileName, string json, PayloadRegistry payloads, CategoryRegistry categories)
		{
			ParseResult parseResult = new ParseResult();
			JsonDocument jsonDocument;
			try
			{
				jsonDocument = JsonDocument.Parse(json, Options);
			}
			catch (JsonException ex)
			{
				parseResult.Errors.Add(fileName + ": invalid JSON: " + ex.Message);
				return parseResult;
			}
			using (jsonDocument)
			{
				if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array)
				{
					parseResult.Errors.Add(fileName + ": the file must be a JSON array of definitions.");
					return parseResult;
				}
				int num = 0;
				foreach (JsonElement item in jsonDocument.RootElement.EnumerateArray())
				{
					string text = fileName + " [" + num++ + "]";
					try
					{
						parseResult.Definitions.Add(ParseEntry(fileName, item, payloads, categories));
					}
					catch (FieldException ex2)
					{
						parseResult.Errors.Add(text + " " + ex2.Field + ": " + ex2.Message);
					}
				}
				return parseResult;
			}
		}

		public static bool IsNamespacedId(string id)
		{
			int num = 1;
			int num2 = 0;
			for (int i = 0; i < id.Length; i++)
			{
				bool flag;
				switch (id[i])
				{
				case '.':
					if (num2 == 0)
					{
						return false;
					}
					num++;
					num2 = 0;
					continue;
				case '-':
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
				case 'G':
				case 'H':
				case 'I':
				case 'J':
				case 'K':
				case 'L':
				case 'M':
				case 'N':
				case 'O':
				case 'P':
				case 'Q':
				case 'R':
				case 'S':
				case 'T':
				case 'U':
				case 'V':
				case 'W':
				case 'X':
				case 'Y':
				case 'Z':
				case '_':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
				case 'k':
				case 'l':
				case 'm':
				case 'n':
				case 'o':
				case 'p':
				case 'q':
				case 'r':
				case 's':
				case 't':
				case 'u':
				case 'v':
				case 'w':
				case 'x':
				case 'y':
				case 'z':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (!flag)
				{
					return false;
				}
				num2++;
			}
			if (num2 > 0)
			{
				return num >= 2;
			}
			return false;
		}

		private static OrdnanceDefinition ParseEntry(string fileName, JsonElement entry, PayloadRegistry payloads, CategoryRegistry categories)
		{
			//IL_04e9: Expected O, but got Unknown
			if (entry.ValueKind != JsonValueKind.Object)
			{
				throw new FieldException("(entry)", "must be an object.");
			}
			foreach (JsonProperty item in entry.EnumerateObject())
			{
				if (Array.IndexOf(Fields, item.Name) < 0)
				{
					throw new FieldException(item.Name, "unknown field.");
				}
			}
			string text = RequiredString(entry, "Id");
			if (text.Trim() != text || text.Any(char.IsWhiteSpace))
			{
				throw new FieldException("Id", "must not contain spaces.");
			}
			if (text.Length > 64)
			{
				throw new FieldException("Id", "is longer than " + 64 + " characters.");
			}
			if (!IsNamespacedId(text))
			{
				throw new FieldException("Id", "'" + text + "' must start with the author's namespace: segments of letters, digits, '_' or '-' joined by dots, at least two (author.name).");
			}
			JsonElement value;
			string name = (entry.TryGetProperty("Name", out value) ? RequiredString(entry, "Name") : text);
			bool flag = true;
			if (entry.TryGetProperty("Callable", out var value2))
			{
				JsonValueKind valueKind = value2.ValueKind;
				if (valueKind - 5 > JsonValueKind.Object)
				{
					throw new FieldException("Callable", "must be true or false.");
				}
				flag = value2.GetBoolean();
			}
			if (!flag)
			{
				string[] callerFields = CallerFields;
				foreach (string text2 in callerFields)
				{
					if (entry.TryGetProperty(text2, out value))
					{
						throw new FieldException(text2, "must not be given when Callable is false: players never call this entry, only a level's Drops or Place do.");
					}
				}
			}
			string category = "";
			uint categoryBeam = 0u;
			if (flag)
			{
				string text3 = RequiredString(entry, "Category");
				if (!categories.TryGet(text3, out OrdnanceCategoryInfo info))
				{
					throw new FieldException("Category", "'" + text3 + "' is not one of " + string.Join(", ", categories.Ordered.Select((OrdnanceCategoryInfo c) => c.Id)) + ".");
				}
				category = info.Id;
				categoryBeam = info.BeamColour;
			}
			string text4 = (entry.TryGetProperty("Icon", out value) ? RequiredString(entry, "Icon") : "");
			if (text4.Length != 0 && !IsIconFileName(text4))
			{
				throw new FieldException("Icon", "must be the file name of a .png next to this definition file, without folders.");
			}
			string text5 = (entry.TryGetProperty("Description", out value) ? RequiredString(entry, "Description") : "");
			if (text5.Length > 400)
			{
				throw new FieldException("Description", "is longer than " + 400 + " characters.");
			}
			OrdnanceStat[] stats = ParseStats(entry);
			Direction[] code = (flag ? ParseCode(entry) : Array.Empty<Direction>());
			double cooldown = (flag ? Number(entry, "Cooldown", 0.0, 3600.0) : 0.0);
			double num = (entry.TryGetProperty("Uses", out value) ? Number(entry, "Uses", 1.0, 99.0) : (-1.0));
			if (num != Math.Floor(num))
			{
				throw new FieldException("Uses", "must be a whole number.");
			}
			double callInDelay = (flag ? Number(entry, "CallInDelay", 0.0, 120.0) : 0.0);
			uint? beamColour = null;
			if (entry.TryGetProperty("BeamColour", out value))
			{
				string text6 = RequiredString(entry, "BeamColour");
				if (!TryParseColour(text6, out var rgb))
				{
					throw new FieldException("BeamColour", "'" + text6 + "' must be a colour written #RRGGBB (six hex digits).");
				}
				beamColour = rgb;
			}
			if (!entry.TryGetProperty("Payload", out var value3) || value3.ValueKind != JsonValueKind.Object)
			{
				throw new FieldException("Payload", "is required and must be an object.");
			}
			string text7 = RequiredString(value3, "Type", "Payload.Type");
			if (!payloads.TryGet(text7, out IOrdnancePayloadParser parser))
			{
				throw new FieldException("Payload.Type", "'" + text7 + "' is not a registered payload type (known: " + string.Join(", ", payloads.Types.OrderBy<string, string>((string t) => t, StringComparer.Ordinal)) + ").");
			}
			object obj;
			try
			{
				obj = parser.Parse(value3);
			}
			catch (StrictJsonException ex)
			{
				StrictJsonException ex2 = ex;
				throw new FieldException("Payload." + ex2.Field, ((Exception)(object)ex2).Message);
			}
			if (obj == null)
			{
				throw new FieldException("Payload", "the '" + text7 + "' parser returned no parameters.");
			}
			return new OrdnanceDefinition(text, name, category, categoryBeam, text4, text5, code, cooldown, (int)num, callInDelay, text7, value3.Clone(), obj, fileName, beamColour, stats, flag);
		}

		private static OrdnanceStat[] ParseStats(JsonElement entry)
		{
			if (!entry.TryGetProperty("Stats", out var value))
			{
				return Array.Empty<OrdnanceStat>();
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				throw new FieldException("Stats", "must be an array of { \"Name\", \"Value\", \"Max\" } rows.");
			}
			List<OrdnanceStat> list = new List<OrdnanceStat>();
			foreach (JsonElement item in value.EnumerateArray())
			{
				string text = "Stats[" + list.Count + "]";
				if (list.Count == 6)
				{
					throw new FieldException("Stats", "has more than " + 6 + " rows.");
				}
				if (item.ValueKind != JsonValueKind.Object)
				{
					throw new FieldException(text, "must be an object.");
				}
				foreach (JsonProperty item2 in item.EnumerateObject())
				{
					if (Array.IndexOf(StatFields, item2.Name) < 0)
					{
						throw new FieldException(text + "." + item2.Name, "unknown field.");
					}
				}
				string text2 = RequiredString(item, "Name", text + ".Name");
				if (text2.Length > 16)
				{
					throw new FieldException(text + ".Name", "is longer than " + 16 + " characters.");
				}
				JsonElement value2;
				int max = (item.TryGetProperty("Max", out value2) ? WholeNumber(item, "Max", text + ".Max", 1, 10) : 5);
				int value3 = WholeNumber(item, "Value", text + ".Value", 0, max);
				list.Add(new OrdnanceStat(text2, value3, max));
			}
			return list.ToArray();
		}

		private static int WholeNumber(JsonElement owner, string field, string label, int min, int max)
		{
			if (!owner.TryGetProperty(field, out var value))
			{
				throw new FieldException(label, "is required.");
			}
			if (value.ValueKind != JsonValueKind.Number || !value.TryGetInt32(out var value2))
			{
				throw new FieldException(label, "must be a whole number.");
			}
			if (value2 < min || value2 > max)
			{
				throw new FieldException(label, value2 + " is outside " + min + ".." + max + ".");
			}
			return value2;
		}

		public static bool TryParseColour(string text, out uint rgb)
		{
			rgb = 0u;
			if (text.Length == 7 && text[0] == '#')
			{
				return uint.TryParse(text.AsSpan(1), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out rgb);
			}
			return false;
		}

		public static bool IsIconFileName(string icon)
		{
			if (icon.Trim() == icon && icon.Length > 4 && icon.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && icon.IndexOfAny(new char[3] { '/', '\\', ':' }) < 0)
			{
				return !icon.StartsWith("..", StringComparison.Ordinal);
			}
			return false;
		}

		private static string RequiredString(JsonElement owner, string field, string? label = null)
		{
			if (label == null)
			{
				label = field;
			}
			if (!owner.TryGetProperty(field, out var value))
			{
				throw new FieldException(label, "is required.");
			}
			if (value.ValueKind != JsonValueKind.String)
			{
				throw new FieldException(label, "must be a string.");
			}
			string? text = value.GetString();
			if (text.Length == 0)
			{
				throw new FieldException(label, "must not be empty.");
			}
			return text;
		}

		private static double Number(JsonElement owner, string field, double min, double max)
		{
			if (!owner.TryGetProperty(field, out var value))
			{
				throw new FieldException(field, "is required.");
			}
			if (value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out var value2) || double.IsNaN(value2) || double.IsInfinity(value2))
			{
				throw new FieldException(field, "must be a finite number.");
			}
			if (value2 < min || value2 > max)
			{
				throw new FieldException(field, value2.ToString(CultureInfo.InvariantCulture) + " is outside " + min.ToString(CultureInfo.InvariantCulture) + ".." + max.ToString(CultureInfo.InvariantCulture) + ".");
			}
			return value2;
		}

		private static Direction[] ParseCode(JsonElement entry)
		{
			if (!entry.TryGetProperty("Code", out var value))
			{
				throw new FieldException("Code", "is required.");
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				throw new FieldException("Code", "must be an array of Up, Down, Left, Right.");
			}
			List<Direction> list = new List<Direction>();
			foreach (JsonElement item in value.EnumerateArray())
			{
				switch ((item.ValueKind == JsonValueKind.String) ? item.GetString() : null)
				{
				case "Up":
					list.Add(Direction.Up);
					break;
				case "Down":
					list.Add(Direction.Down);
					break;
				case "Left":
					list.Add(Direction.Left);
					break;
				case "Right":
					list.Add(Direction.Right);
					break;
				default:
					throw new FieldException("Code", "step " + list.Count + " must be exactly Up, Down, Left or Right.");
				}
			}
			if (list.Count == 0 || list.Count > 8)
			{
				throw new FieldException("Code", "needs 1.." + 8 + " steps, has " + list.Count + ".");
			}
			return list.ToArray();
		}
	}
	internal sealed class DefinitionSet
	{
		private readonly Dictionary<uint, OrdnanceDefinition> _byKey = new Dictionary<uint, OrdnanceDefinition>();

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

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

		public ulong Hash { get; private set; }

		public static DefinitionSet Empty { get; } = Build(Array.Empty<OrdnanceDefinition>(), new List<string>());

		private DefinitionSet()
		{
		}

		public bool TryGet(uint key, out OrdnanceDefinition definition)
		{
			return _byKey.TryGetValue(key, out definition);
		}

		public bool TryGetById(string id, out OrdnanceDefinition definition)
		{
			return _byId.TryGetValue(id, out definition);
		}

		public static DefinitionSet Build(IEnumerable<OrdnanceDefinition> definitions, List<string> errors)
		{
			DefinitionSet definitionSet = new DefinitionSet();
			foreach (OrdnanceDefinition definition in definitions)
			{
				OrdnanceDefinition value2;
				if (definitionSet._byId.TryGetValue(definition.Id, out OrdnanceDefinition value))
				{
					errors.Add(definition.SourceFile + ": Id '" + definition.Id + "' is already defined in " + value.SourceFile + "; this entry is skipped.");
				}
				else if (definitionSet._byKey.TryGetValue(definition.Key, out value2))
				{
					errors.Add(definition.SourceFile + ": Id '" + definition.Id + "' has the same network hash as '" + value2.Id + "' (" + value2.SourceFile + "); rename one. This entry is skipped.");
				}
				else
				{
					definitionSet._byId.Add(definition.Id, definition);
					definitionSet._byKey.Add(definition.Key, definition);
				}
			}
			definitionSet.Definitions = definitionSet._byId.Values.OrderBy<OrdnanceDefinition, string>((OrdnanceDefinition d) => d.Id, StringComparer.Ordinal).ToArray();
			definitionSet.Hash = Hashing.SetHash(definitionSet.Definitions);
			return definitionSet;
		}
	}
	public enum DropWhen
	{
		LevelStart,
		Event
	}
	public sealed record DropPoint(int Area, float X, float Y, float Z)
	{
		internal bool IsWorld => Area < 0;

		[CompilerGenerated]
		private bool PrintMembers(StringBuilder builder)
		{
			RuntimeHelpers.EnsureSufficientExecutionStack();
			builder.Append("Area = ");
			builder.Append(Area.ToString());
			builder.Append(", X = ");
			builder.Append(X.ToString());
			builder.Append(", Y = ");
			builder.Append(Y.ToString());
			builder.Append(", Z = ");
			builder.Append(Z.ToString());
			return true;
		}
	}
	public sealed record DropWhere(int Dimension, int Layer, int LocalIndex, DropPoint Point)
	{
		public override string ToString()
		{
			return "dimension " + Dimension + ", layer " + Layer + ", zone " + LocalIndex + ", " + (Point.IsWorld ? ("position " + DeveloperDrops.Format(Point.X, Point.Y, Point.Z)) : ("area " + Point.Area + " offset " + DeveloperDrops.Format(Point.X, Point.Y, Point.Z)));
		}
	}
	public sealed record OrdnanceLevelDrop(string Name, string Ordnance, DropWhen When, OrdnanceArrivalMode Arrival, double Delay, float Yaw, DropWhere? Where);
	internal interface IDropLevel
	{
		bool HasDimension(int dimension);

		bool TryZone(int dimension, int layer, int localIndex, out int areaCount);

		Vec3 AreaCentre(int dimension, int layer, int localIndex, int area);
	}
	internal static class DeveloperDrops
	{
		internal const int EventType = 75000;

		internal const double MaxDelay = 3600.0;

		private const int MaxLayer = 2;

		private static readonly string[] EntryFields = new string[7] { "Name", "Ordnance", "When", "Arrival", "Delay", "Yaw", "Where" };

		private static readonly string[] WhereFields = new string[6] { "DimensionIndex", "Layer", "LocalIndex", "Position", "Area", "Offset" };

		private static readonly string[] VectorFields = new string[3] { "x", "y", "z" };

		internal static uint LevelStartCall(string dropName)
		{
			return Fnv.Hash32("LevelStart:" + dropName) | 0x80000000u;
		}

		internal static uint SeedCalls(ulong clock)
		{
			return (uint)((int)(clock & 0x7FFFFFFF) | 1);
		}

		internal static uint NextCall(ref uint next)
		{
			uint result = next;
			next = (next + 1) & 0x7FFFFFFF;
			if (next == 0)
			{
				next = 1u;
			}
			return result;
		}

		private static bool NumberTaken(Dictionary<uint, string> numbers, string name, string at, List<string> errors)
		{
			uint key = LevelStartCall(name);
			if (numbers.TryGetValue(key, out string value))
			{
				errors.Add(at + " Name: '" + name + "' has the same call number as '" + value + "'; rename one.");
				return true;
			}
			numbers[key] = name;
			return false;
		}

		internal static string Format(float x, float y, float z)
		{
			return "(" + x.ToString("0.##", CultureInfo.InvariantCulture) + ", " + y.ToString("0.##", CultureInfo.InvariantCulture) + ", " + z.ToString("0.##", CultureInfo.InvariantCulture) + ")";
		}

		internal static IReadOnlyList<OrdnanceLevelDrop> Parse(JsonElement array, string where, List<string> errors)
		{
			List<OrdnanceLevelDrop> list = new List<OrdnanceLevelDrop>();
			if (array.ValueKind != JsonValueKind.Array)
			{
				errors.Add(where + " Drops: must be an array of drops.");
				return list;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Dictionary<uint, string> numbers = new Dictionary<uint, string>();
			int num = 0;
			foreach (JsonElement item in array.EnumerateArray())
			{
				string text = where + " Drops[" + num++ + "]";
				int count = errors.Count;
				OrdnanceLevelDrop ordnanceLevelDrop = ParseEntry(item, text, errors);
				if (!(ordnanceLevelDrop == null) && errors.Count == count)
				{
					if (!hashSet.Add(ordnanceLevelDrop.Name))
					{
						errors.Add(text + " Name: '" + ordnanceLevelDrop.Name + "' is used by another drop of this list.");
					}
					else if (!NumberTaken(numbers, ordnanceLevelDrop.Name, text, errors))
					{
						list.Add(ordnanceLevelDrop);
					}
				}
			}
			return list;
		}

		private static OrdnanceLevelDrop? ParseEntry(JsonElement entry, string at, List<string> errors)
		{
			if (entry.ValueKind != JsonValueKind.Object)
			{
				errors.Add(at + ": must be an object.");
				return null;
			}
			Unknown(entry, EntryFields, at, errors);
			string text = Text(entry, "Name", at, errors);
			string text2 = Text(entry, "Ordnance", at, errors);
			string text3 = Text(entry, "When", at, errors);
			DropWhen dropWhen = ((text3 == "Event") ? DropWhen.Event : DropWhen.LevelStart);
			bool flag;
			switch (text3)
			{
			case null:
			case "LevelStart":
			case "Event":
				flag = true;
				break;
			default:
				flag = false;
				break;
			}
			if (!flag)
			{
				errors.Add(at + " When: must be \"LevelStart\" or \"Event\".");
			}
			OrdnanceArrivalMode mode = OrdnanceArrivalMode.Drop;
			if (entry.TryGetProperty("Arrival", out var value) && (value.ValueKind != JsonValueKind.String || !ArrivalPlan.TryParse(value.GetString(), out mode)))
			{
				errors.Add(at + " Arrival: must be \"Drop\" or \"Direct\".");
			}
			double value2 = 0.0;
			if (entry.TryGetProperty("Delay", out var value3))
			{
				if (dropWhen == DropWhen.Event)
				{
					errors.Add(at + " Delay: an event drop waits for its event's own Delay; remove this field.");
				}
				else if (!Number(value3, 0.0, 3600.0, out value2))
				{
					errors.Add(at + " Delay: must be seconds 0.." + 3600.0 + ".");
				}
			}
			double value4 = 0.0;
			if (entry.TryGetProperty("Yaw", out var value5) && !Number(value5, 0.0, 360.0, out value4))
			{
				errors.Add(at + " Yaw: must be degrees 0..360.");
			}
			DropWhere dropWhere = null;
			if (entry.TryGetProperty("Where", out var value6))
			{
				dropWhere = ParseWhere(value6, at + " Where", errors);
			}
			else if (dropWhen == DropWhen.LevelStart)
			{
				errors.Add(at + " Where: a level-start drop needs a place.");
			}
			if (text == null || text2 == null || text3 == null)
			{
				return null;
			}
			return new OrdnanceLevelDrop(text, text2, dropWhen, mode, value2, (float)value4, dropWhere);
		}

		private static DropWhere? ParseWhere(JsonElement element, string at, List<string> errors)
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				errors.Add(at + ": must be an object.");
				return null;
			}
			Unknown(element, WhereFields, at, errors);
			int num = Whole(element, "DimensionIndex", 0, 255, at, errors);
			int num2 = Whole(element, "Layer", 0, 2, at, errors);
			int num3 = Whole(element, "LocalIndex", 0, 255, at, errors);
			JsonElement value;
			bool flag = element.TryGetProperty("Position", out value);
			JsonElement value2;
			bool flag2 = element.TryGetProperty("Area", out value2);
			JsonElement value3;
			bool flag3 = element.TryGetProperty("Offset", out value3);
			DropPoint dropPoint = null;
			if (flag == flag2)
			{
				errors.Add(at + ": give either Position (a world point) or Area (an area of the zone, with an optional Offset).");
			}
			else if (flag)
			{
				if (flag3)
				{
					errors.Add(at + " Offset: only goes with Area.");
				}
				Vec3? vec = Vector(value, at + " Position", errors);
				if (vec.HasValue)
				{
					Vec3 valueOrDefault = vec.GetValueOrDefault();
					dropPoint = new DropPoint(-1, valueOrDefault.X, valueOrDefault.Y, valueOrDefault.Z);
				}
			}
			else
			{
				int num4 = -1;
				if (value2.ValueKind == JsonValueKind.Number && value2.TryGetInt32(out var value4) && value4 >= 0)
				{
					num4 = value4;
				}
				else
				{
					errors.Add(at + " Area: must be a whole number from 0 (the zone's first area).");
				}
				Vec3? vec2 = (flag3 ? Vector(value3, at + " Offset", errors) : new Vec3?(new Vec3(0f, 0f, 0f)));
				if (num4 >= 0 && vec2.HasValue)
				{
					Vec3 valueOrDefault2 = vec2.GetValueOrDefault();
					dropPoint = new DropPoint(num4, valueOrDefault2.X, valueOrDefault2.Y, valueOrDefault2.Z);
				}
			}
			if (num >= 0 && num2 >= 0 && num3 >= 0 && !(dropPoint == null))
			{
				return new DropWhere(num, num2, num3, dropPoint);
			}
			return null;
		}

		internal static void Check(IReadOnlyList<OrdnanceLevelDrop> drops, string where, List<string> errors)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Dictionary<uint, string> numbers = new Dictionary<uint, string>();
			for (int num = 0; num < drops.Count; num++)
			{
				OrdnanceLevelDrop ordnanceLevelDrop = drops[num];
				string text = where + " Drops[" + num + "]";
				if (string.IsNullOrWhiteSpace(ordnanceLevelDrop.Name))
				{
					errors.Add(text + " Name: must be a non-empty string.");
				}
				else if (!hashSet.Add(ordnanceLevelDrop.Name))
				{
					errors.Add(text + " Name: '" + ordnanceLevelDrop.Name + "' is used by another drop of this list.");
				}
				else
				{
					NumberTaken(numbers, ordnanceLevelDrop.Name, text, errors);
				}
				if (string.IsNullOrWhiteSpace(ordnanceLevelDrop.Ordnance))
				{
					errors.Add(text + " Ordnance: must be a non-empty string.");
				}
				if (!Enum.IsDefined(ordnanceLevelDrop.When))
				{
					errors.Add(text + " When: must be LevelStart or Event.");
				}
				if (!Enum.IsDefined(ordnanceLevelDrop.Arrival))
				{
					errors.Add(text + " Arrival: must be Drop or Direct.");
				}
				bool num2;
				if (ordnanceLevelDrop.When != DropWhen.Event)
				{
					if (!double.IsFinite(ordnanceLevelDrop.Delay) || ordnanceLevelDrop.Delay < 0.0)
					{
						goto IL_0147;
					}
					num2 = ordnanceLevelDrop.Delay > 3600.0;
				}
				else
				{
					num2 = ordnanceLevelDrop.Delay != 0.0;
				}
				if (num2)
				{
					goto IL_0147;
				}
				goto IL_018a;
				IL_0147:
				errors.Add(text + " Delay: " + ((ordnanceLevelDrop.When == DropWhen.Event) ? "an event drop waits for its event's own Delay; leave it 0." : ("must be seconds 0.." + 3600.0 + ".")));
				goto IL_018a;
				IL_018a:
				if (!float.IsFinite(ordnanceLevelDrop.Yaw) || ordnanceLevelDrop.Yaw < 0f || ordnanceLevelDrop.Yaw > 360f)
				{
					errors.Add(text + " Yaw: must be degrees 0..360.");
				}
				if (ordnanceLevelDrop.Where == null)
				{
					if (ordnanceLevelDrop.When == DropWhen.LevelStart)
					{
						errors.Add(text + " Where: a level-start drop needs a place.");
					}
				}
				else
				{
					DropWhere dropWhere = ordnanceLevelDrop.Where;
					int dimension = dropWhere.Dimension;
					bool flag = ((dimension < 0 || dimension > 255) ? true : false);
					bool flag2 = flag;
					if (!flag2)
					{
						int localIndex = dropWhere.LocalIndex;
						bool flag3 = ((localIndex < 0 || localIndex > 255) ? true : false);
						flag2 = flag3;
					}
					bool flag4 = flag2;
					if (!flag4)
					{
						int localIndex = dropWhere.Layer;
						bool flag3 = ((localIndex < 0 || localIndex > 2) ? true : false);
						flag4 = flag3;
					}
					if (flag4 || dropWhere.Point == null || !float.IsFinite(dropWhere.Point.X) || !float.IsFinite(dropWhere.Point.Y) || !float.IsFinite(dropWhere.Point.Z))
					{
						errors.Add(text + " Where: dimension 0..255, layer 0.." + 2 + ", zone 0..255 and a finite point are required.");
					}
				}
			}
		}

		internal static IReadOnlyList<OrdnanceLevelDrop> Usable(IReadOnlyList<OrdnanceLevelDrop> drops, DefinitionSet set, Func<string, bool> supportsDirect, string where, List<string> errors)
		{
			List<OrdnanceLevelDrop> list = new List<OrdnanceLevelDrop>(drops.Count);
			foreach (OrdnanceLevelDrop drop in drops)
			{
				if (!set.TryGetById(drop.Ordnance, out OrdnanceDefinition definition))
				{
					errors.Add(where + " drop '" + drop.Name + "' Ordnance: '" + drop.Ordnance + "' is not a loaded call-in; this drop is skipped.");
				}
				else if (drop.Arrival == OrdnanceArrivalMode.Direct && !supportsDirect(definition.PayloadType))
				{
					errors.Add(where + " drop '" + drop.Name + "' Arrival: '" + drop.Ordnance + "' (payload " + definition.PayloadType + ") cannot arrive Direct; this drop is skipped.");
				}
				else
				{
					list.Add(drop);
				}
			}
			return list;
		}

		internal static bool Fires(int type, int eventTrigger, int trigger, bool ignoreTrigger, float delay, float currentDuration, out float wait)
		{
			wait = 0f;
			if (type != 75000)
			{
				return false;
			}
			if (!ignoreTrigger && eventTrigger != trigger)
			{
				return false;
			}
			if (currentDuration != 0f && delay <= currentDuration)
			{
				return false;
			}
			wait = Math.Max(0f, delay - currentDuration);
			return true;
		}

		private static void Unknown(JsonElement owner, string[] known, string at, List<string> errors)
		{
			foreach (JsonProperty item in owner.EnumerateObject())
			{
				if (Array.IndexOf(known, item.Name) < 0)
				{
					errors.Add(at + " " + item.Name + ": unknown field.");
				}
			}
		}

		private static string? Text(JsonElement owner, string field, string at, List<string> errors)
		{
			if (owner.TryGetProperty(field, out var value) && value.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(value.GetString()))
			{
				return value.GetString();
			}
			errors.Add(at + " " + field + ": " + (owner.TryGetProperty(field, out var _) ? "must be a non-empty string." : "is required."));
			return null;
		}

		private static int Whole(JsonElement owner, string field, int min, int max, string at, List<string> errors)
		{
			if (!owner.TryGetProperty(field, out var value))
			{
				errors.Add(at + " " + field + ": is required.");
				return -1;
			}
			if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var value2) && value2 >= min && value2 <= max)
			{
				return value2;
			}
			errors.Add(at + " " + field + ": must be a whole number " + min + ".." + max + ".");
			return -1;
		}

		private static bool Number(JsonElement element, double min, double max, out double value)
		{
			value = 0.0;
			if (element.ValueKind != JsonValueKind.Number || !element.TryGetDouble(out var value2) || !double.IsFinite(value2) || value2 < min || value2 > max)
			{
				return false;
			}
			value = value2;
			return true;
		}

		private static Vec3? Vector(JsonElement element, string at, List<string> errors)
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				errors.Add(at + ": must be an object { \"x\": .., \"y\": .., \"z\": .. }.");
				return null;
			}
			Unknown(element, VectorFields, at, errors);
			float[] array = new float[3];
			for (int i = 0; i < 3; i++)
			{
				if (element.TryGetProperty(VectorFields[i], out var value) && value.ValueKind == JsonValueKind.Number && value.TryGetDouble(out var value2) && double.IsFinite(value2) && Math.Abs(value2) <= 100000.0)
				{
					array[i] = (float)value2;
					continue;
				}
				errors.Add(at + " " + VectorFields[i] + ": must be a finite number.");
				return null;
			}
			return new Vec3(array[0], array[1], array[2]);
		}
	}
	internal static class DropPlacement
	{
		internal static string? Resolve(DropWhere where, IDropLevel level, out Vec3 point)
		{
			point = default(Vec3);
			if (!level.HasDimension(where.Dimension))
			{
				return "dimension " + where.Dimension + " does not exist in this level";
			}
			if (!level.TryZone(where.Dimension, where.Layer, where.LocalIndex, out var areaCount))
			{
				return "there is no zone " + where.LocalIndex + " in layer " + where.Layer + " of dimension " + where.Dimension;
			}
			DropPoint point2 = where.Point;
			if (point2.IsWorld)
			{
				point = new Vec3(point2.X, point2.Y, point2.Z);
				return null;
			}
			if (point2.Area >= areaCount)
			{
				return "zone " + where.LocalIndex + " (layer " + where.Layer + ", dimension " + where.Dimension + ") has " + areaCount + " area(s); Area " + point2.Area + " does not exist";
			}
			Vec3 vec = level.AreaCentre(where.Dimension, where.Layer, where.LocalIndex, point2.Area);
			point = new Vec3(vec.X + point2.X, vec.Y + point2.Y, vec.Z + point2.Z);
			return null;
		}
	}
	internal static class Hashing
	{
		internal static ulong TypesHash(IEnumerable<string> types)
		{
			return BitConverter.ToUInt64(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(types.OrderBy<string, string>((string t) => t, StringComparer.Ordinal))), 0);
		}

		public static ulong SetHash(IEnumerable<OrdnanceDefinition> definitions)
		{
			return BitConverter.ToUInt64(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(from d in definitions.OrderBy<OrdnanceDefinition, string>((OrdnanceDefinition d) => d.Id, StringComparer.Ordinal)
				select new
				{
					Id = d.Id,
					Name = d.Name,
					Callable = d.Callable,
					Category = d.Category,
					Icon = d.Icon,
					Description = d.Description,
					Stats = d.Stats.Select((OrdnanceStat s) => new { s.Name, s.Value, s.Max }).ToArray(),
					Code = d.Code.Select((Direction c) => (int)c).ToArray(),
					Cooldown = d.Cooldown,
					Uses = d.Uses,
					CallInDelay = d.CallInDelay,
					BeamColour = d.BeamColour,
					PayloadType = d.PayloadType,
					Payload = StrictJson.Canonical(d.Payload)
				})), 0);
		}
	}
	internal enum HelloDecision
	{
		Ignore,
		Challenge,
		Accept
	}
	internal sealed class HelloAdmission
	{
		private readonly HashSet<(ulong Player, ulong Greeting)> _completed = new HashSet<(ulong, ulong)>();

		internal HelloDecision Evaluate(ulong player, ulong greeting, ulong receivedEpoch, ulong currentEpoch)
		{
			if (greeting == 0L)
			{
				return HelloDecision.Ignore;
			}
			if (receivedEpoch == 0L)
			{
				return HelloDecision.Challenge;
			}
			if (receivedEpoch != currentEpoch || !_completed.Add((player, greeting)))
			{
				return HelloDecision.Ignore;
			}
			return HelloDecision.Accept;
		}

		internal void Clear()
		{
			_completed.Clear();
		}
	}
	internal sealed class HostRulesSnapshot
	{
		internal string LevelKey { get; }

		internal ulong DefinitionHash { get; }

		internal int LoadoutSize { get; }

		internal uint[]? AllowedKeys { get; }

		internal double CooldownMultiplier { get; }

		internal double UsesMultiplier { get; }

		internal double MaxThrowDistance { get; }

		internal bool CallMenu { get; }

		internal HostRulesSnapshot(string levelKey, ulong definitionHash, int loadoutSize, uint[]? allowedKeys, double cooldownMultiplier, double usesMultiplier, double maxThrowDistance, bool callMenu)
		{
			LevelKey = levelKey;
			DefinitionHash = definitionHash;
			LoadoutSize = loadoutSize;
			AllowedKeys = allowedKeys;
			CooldownMultiplier = cooldownMultiplier;
			UsesMultiplier = usesMultiplier;
			MaxThrowDistance = maxThrowDistance;
			CallMenu = callMenu;
		}

		internal static HostRulesSnapshot From(string? levelKey, DefinitionSet set, OrdnanceLevelRules rules)
		{
			uint[] allowedKeys = null;
			if (rules.Allowed != null)
			{
				allowedKeys = (from key in (from id in rules.Allowed
						select (!set.TryGetById(id, out OrdnanceDefinition definition)) ? ((uint?)null) : new uint?(definition.Key) into key
						where key.HasValue
						select key.Value).Distinct()
					orderby key
					select key).ToArray();
			}
			return new HostRulesSnapshot(levelKey ?? "", set.Hash, rules.LoadoutSize, allowedKeys, rules.CooldownMultiplier, rules.UsesMultiplier, rules.MaxThrowDistance, rules.CallMenu);
		}

		internal OrdnanceLevelRules ToSettings(DefinitionSet set)
		{
			IReadOnlyList<string> allowed = null;
			if (AllowedKeys != null)
			{
				List<string> list = new List<string>(AllowedKeys.Length);
				uint[] allowedKeys = AllowedKeys;
				foreach (uint key in allowedKeys)
				{
					if (set.TryGet(key, out OrdnanceDefinition definition))
					{
						list.Add(definition.Id);
					}
				}
				allowed = list;
			}
			return new OrdnanceLevelRules
			{
				LoadoutSize = LoadoutSize,
				Allowed = allowed,
				CooldownMultiplier = CooldownMultiplier,
				UsesMultiplier = UsesMultiplier,
				MaxThrowDistance = MaxThrowDistance,
				CallMenu = CallMenu
			};
		}
	}
	internal static class HostRulesWire
	{
		private const uint Magic = 1397903174u;

		internal const int MaxPayloadBytes = 8192;

		internal const int MaxLevelKeyBytes = 512;

		internal const int MaxAllowedKeys = 1900;

		private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		internal static byte[] Encode(HostRulesSnapshot rules)
		{
			byte[] bytes = Utf8.GetBytes(rules.LevelKey);
			if (bytes.Length > 512)
			{
				throw new InvalidOperationException("The expedition key is too long to synchronize.");
			}
			uint[] allowedKeys = rules.AllowedKeys;
			if (allowedKeys != null && allowedKeys.Length > 1900)
			{
				throw new InvalidOperationException("The allowed call-in list is too long to synchronize.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Utf8, leaveOpen: true))
			{
				binaryWriter.Write(1397903174u);
				binaryWriter.Write(rules.DefinitionHash);
				binaryWriter.Write((ushort)bytes.Length);
				binaryWriter.Write(bytes);
				binaryWriter.Write((byte)rules.LoadoutSize);
				binaryWriter.Write(rules.CooldownMultiplier);
				binaryWriter.Write(rules.UsesMultiplier);
				binaryWriter.Write(rules.MaxThrowDistance);
				binaryWriter.Write(rules.CallMenu);
				uint[]? allowedKeys2 = rules.AllowedKeys;
				binaryWriter.Write((short)((allowedKeys2 != null) ? allowedKeys2.Length : (-1)));
				if (rules.AllowedKeys != null)
				{
					allowedKeys = rules.AllowedKeys;
					foreach (uint value in allowedKeys)
					{
						binaryWriter.Write(value);
					}
				}
			}
			byte[] array = memoryStream.ToArray();
			if (array.Length > 8192)
			{
				throw new InvalidOperationException("The host rules are too large to synchronize.");
			}
			return array;
		}

		internal static bool TryDecode(byte[] bytes, out HostRulesSnapshot? rules)
		{
			rules = null;
			if (bytes == null || bytes.Length > 8192 || bytes.Length < 42)
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(bytes, writable: false);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Utf8, leaveOpen: true);
				if (binaryReader.ReadUInt32() != 1397903174)
				{
					return false;
				}
				ulong definitionHash = binaryReader.ReadUInt64();
				ushort num = binaryReader.ReadUInt16();
				if (num > 512 || num > memoryStream.Length - memoryStream.Position)
				{
					return false;
				}
				byte[] array = binaryReader.ReadBytes(num);
				if (array.Length != num)
				{
					return false;
				}
				string levelKey = Utf8.GetString(array);
				byte b = binaryReader.ReadByte();
				double num2 = binaryReader.ReadDouble();
				double num3 = binaryReader.ReadDouble();
				double num4 = binaryReader.ReadDouble();
				byte b2 = binaryReader.ReadByte();
				short num5 = binaryReader.ReadInt16();
				bool flag = ((b < 1 || b > 8) ? true : false);
				if (flag || !Finite(num2, 0.0, 10.0) || !Finite(num3, 0.0, 10.0) || !Finite(num4, 5.0, 500.0) || b2 > 1 || num5 < -1 || num5 > 1900)
				{
					return false;
				}
				uint[] array2 = null;
				if (num5 >= 0)
				{
					if (memoryStream.Length - memoryStream.Position != num5 * 4)
					{
						return false;
					}
					array2 = new uint[num5];
					HashSet<uint> hashSet = new HashSet<uint>();
					for (int i = 0; i < num5; i++)
					{
						array2[i] = binaryReader.ReadUInt32();
						if (!hashSet.Add(array2[i]))
						{
							return false;
						}
					}
				}
				if (memoryStream.Position != memoryStream.Length)
				{
					return false;
				}
				rules = new HostRulesSnapshot(levelKey, definitionHash, b, array2, num2, num3, num4, b2 == 1);
				return true;
			}
			catch (Exception ex) when (((ex is EndOfStreamException || ex is DecoderFallbackException || ex is IOException) ? 1 : 0) != 0)
			{
				return false;
			}
		}

		private static bool Finite(double value, double min, double max)
		{
			if (!double.IsNaN(value) && !double.IsInfinity(value) && value >= min)
			{
				return value <= max;
			}
			return false;
		}
	}
	internal readonly record struct ItemEntry(uint ItemId, int Count, float AmmoRatio);
	internal static class ItemList
	{
		public const int MaxPickups = 4;

		private static readonly string[] EntryFields = new string[3] { "Item", "Count", "AmmoRatio" };

		public const float FullPackPoints = 100f;

		public const float PackUsePoints = 20f;

		public static ItemEntry[] Parse(JsonElement payload, Func<uint?, string?, uint?> resolve)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			PayloadReader.OnlyKnown(payload, "Items");
			if (!payload.TryGetProperty("Items", out var value) || value.ValueKind != JsonValueKind.Array)
			{
				throw new StrictJsonException("Items", "is required: a list of { \"Item\": id or name, \"Count\": 1.." + 4 + ", \"AmmoRatio\": 0..1 }.");
			}
			List<ItemEntry> list = new List<ItemEntry>();
			int num = 0;
			int num2 = 0;
			foreach (JsonElement item3 in value.EnumerateArray())
			{
				string text = "Items[" + num2++ + "].";
				StrictJson.OnlyKnown(item3, text, EntryFields, (string[])null, (string)null);
				ValueTuple<uint?, string> valueTuple = StrictJson.Reference(item3, text, "Item");
				uint? item = valueTuple.Item1;
				string item2 = valueTuple.Item2;
				uint itemId = resolve(item, item2) ?? throw new StrictJsonException(text + "Item", "no item data block " + (item.HasValue ? item.Value.ToString() : ("'" + item2 + "'")) + " in this install.");
				int num3 = (int)StrictJson.Integer(item3, text, "Count", (double?)1.0, 1.0, 4.0);
				num += num3;
				list.Add(new ItemEntry(itemId, num3, (float)StrictJson.Number(item3, text, "AmmoRatio", (double?)1.0, 0.0, 1.0)));
			}
			if (list.Count == 0)
			{
				throw new StrictJsonException("Items", "needs at least one item.");
			}
			if (num > 4)
			{
				throw new StrictJsonException("Items", "holds " + num + " pickups; the drop pod carries at most " + 4 + ".");
			}
			return list.ToArray();
		}

		public static uint[] Expand(IReadOnlyList<ItemEntry> entries, out float[] ammoRatios)
		{
			List<uint> list = new List<uint>();
			List<float> list2 = new List<float>();
			foreach (ItemEntry entry in entries)
			{
				for (int i = 0; i < entry.Count; i++)
				{
					list.Add(entry.ItemId);
					list2.Add(entry.AmmoRatio);
				}
			}
			ammoRatios = list2.ToArray();
			return list.ToArray();
		}

		public static float AmmoPoints(int consumableUses, float ratio)
		{
			float num = ((consumableUses > 0) ? 1f : 20f);
			float num2 = ((consumableUses > 0) ? ((float)consumableUses) : 100f);
			if (ratio <= 0f)
			{
				return 0f;
			}
			return Math.Max(num, MathF.Round(ratio * num2 / num, MidpointRounding.AwayFromZero) * num);
		}
	}
	internal enum MatchKind
	{
		None,
		Prefix,
		Exact
	}
	internal readonly struct MatchResult
	{
		public MatchKind Kind { get; }

		public int Index { get; }

		public MatchResult(MatchKind kind, int index)
		{
			Kind = kind;
			Index = index;
		}
	}
	internal static class CommandMatcher
	{
		public static MatchResult Match(IReadOnlyList<OrdnanceDefinition> loadout, IReadOnlyList<Direction> input)
		{
			bool flag = false;
			for (int i = 0; i < loadout.Count; i++)
			{
				IReadOnlyList<Direction> code = loadout[i].Code;
				if (StartsWith(code, input))
				{
					if (code.Count == input.Count)
					{
						return new MatchResult(MatchKind.Exact, i);
					}
					flag = true;
				}
			}
			return new MatchResult(flag ? MatchKind.Prefix : MatchKind.None, -1);
		}

		public static bool StartsWith(IReadOnlyList<Direction> code, IReadOnlyList<Direction> input)
		{
			if (input.Count > code.Count)
			{
				return false;
			}
			for (int i = 0; i < input.Count; i++)
			{
				if (code[i] != input[i])
				{
					return false;
				}
			}
			return true;
		}
	}
	internal sealed class LoadoutResult
	{
		public List<OrdnanceDefinition> Accepted { get; } = new List<OrdnanceDefinition>();

		public Rejection Reason { get; set; }

		public string Detail { get; set; } = "";

		public bool Ok => Reason == Rejection.None;
	}
	internal static class LoadoutValidator
	{
		public static LoadoutResult Validate(IReadOnlyList<uint> keys, DefinitionSet set, OrdnanceLevelRules rules)
		{
			LoadoutResult loadoutResult = new LoadoutResult();
			if (keys.Count > rules.LoadoutSize)
			{
				loadoutResult.Reason = Rejection.LoadoutInvalid;
				loadoutResult.Detail = keys.Count + " entries, the limit is " + rules.LoadoutSize + ".";
				return loadoutResult;
			}
			foreach (uint key in keys)
			{
				if (!set.TryGet(key, out OrdnanceDefinition definition))
				{
					loadoutResult.Reason = Rejection.UnknownDefinition;
					loadoutResult.Detail = "key " + key.ToString("x8") + " is not a definition on the host.";
					return loadoutResult;
				}
				if (!definition.Callable)
				{
					loadoutResult.Reason = Rejection.LoadoutInvalid;
					loadoutResult.Detail = "'" + definition.Id + "' is not callable.";
					return loadoutResult;
				}
				if (loadoutResult.Accepted.Any((OrdnanceDefinition d) => d.Key == key))
				{
					loadoutResult.Reason = Rejection.LoadoutInvalid;
					loadoutResult.Detail = "'" + definition.Id + "' is listed twice.";
					return loadoutResult;
				}
				if (!rules.IsAllowed(definition.Id))
				{
					loadoutResult.Reason = Rejection.LoadoutInvalid;
					loadoutResult.Detail = "'" + definition.Id + "' is not allowed in this level.";
					return loadoutResult;
				}
				loadoutResult.Accepted.Add(definition);
			}
			for (int num = 0; num < loadoutResult.Accepted.Count; num++)
			{
				for (int num2 = 0; num2 < loadoutResult.Accepted.Count; num2++)
				{
					if (num != num2 && CommandMatcher.StartsWith(loadoutResult.Accepted[num2].Code, loadoutResult.Accepted[num].Code))
					{
						loadoutResult.Reason = Rejection.LoadoutInvalid;
						loadoutResult.Detail = "the code of '" + loadoutResult.Accepted[num].Id + "' is the beginning of the code of '" + loadoutResult.Accepted[num2].Id + "' (or equal to it).";
						return loadoutResult;
					}
				}
			}
			return loadoutResult;
		}

		public static OrdnanceDefinition? CodeConflict(IReadOnlyList<OrdnanceDefinition?> slots, int slot, OrdnanceDefinition candidate)
		{
			for (int i = 0; i < slots.Count; i++)
			{
				OrdnanceDefinition ordnanceDefinition = slots[i];
				if (i != slot && ordnanceDefinition != null && ordnanceDefinition.Key != candidate.Key && (CommandMatcher.StartsWith(ordnanceDefinition.Code, candidate.Code) || CommandMatcher.StartsWith(candidate.Code, ordnanceDefinition.Code)))
				{
					return ordnanceDefinition;
				}
			}
			return null;
		}
	}
	internal sealed class LoadoutSave
	{
		public string?[] Slots = Array.Empty<string>();
	}
	internal static class LoadoutFile
	{
		public static string?[] Clean(string?[]? slots, List<string> errors)
		{
			if (slots == null)
			{
				errors.Add("must be an object with a \"Slots\" array.");
				return Array.Empty<string>();
			}
			List<string> list = new List<string>();
			foreach (string text in slots)
			{
				if (list.Count == 8)
				{
					errors.Add("Slots: more than " + 8 + " entries; the rest are ignored.");
					break;
				}
				if (text == null)
				{
					list.Add(null);
					continue;
				}
				if (text.Length > 0)
				{
					list.Add(text);
					continue;
				}
				errors.Add("Slots[" + list.Count + "]: must be a call-in Id or null; left empty.");
				list.Add(null);
			}
			return list.ToArray();
		}
	}
	internal sealed class LoadoutSubmission
	{
		private bool _pending = true;

		internal void Reset()
		{
			_pending = true;
		}

		internal void Changed()
		{
			_pending = true;
		}

		internal void Sent()
		{
			_pending = false;
		}

		internal bool Due(bool hostReady, bool callInProgress)
		{
			if (_pending && hostReady)
			{
				return !callInProgress;
			}
			return false;
		}
	}
	internal static class LobbyPicks
	{
		internal static byte[] Encode(ReadOnlySpan<uint> keys)
		{
			int num = Math.Min(keys.Length, 8);
			byte[] array = new byte[1 + num * 4];
			array[0] = (byte)num;
			for (int i = 0; i < num; i++)
			{
				BinaryPrimitives.WriteUInt32LittleEndian(array.AsSpan(1 + i * 4), keys[i]);
			}
			return array;
		}

		internal static int Decode(ReadOnlySpan<byte> bytes, Span<uint> into)
		{
			if (bytes.Length == 0)
			{
				return -1;
			}
			byte b = bytes[0];
			if (b > 8 || b > into.Length || bytes.Length != 1 + b * 4)
			{
				return -1;
			}
			for (int i = 0; i < b; i++)
			{
				into[i] = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(1 + i * 4));
			}
			return b;
		}
	}
	internal enum Rejection : byte
	{
		None,
		NotInLoadout,
		Cooldown,
		NoUses,
		TooFar,
		InvalidLanding,
		DefinitionMismatch,
		NotHost,
		Disabled,
		UnknownDefinition,
		LoadoutInvalid,
		LandingTimeout,
		CallMenuOff
	}
	internal static class Messages
	{
		public const ushort BeaconItemId = 65114;

		public const int MaxLoadout = 8;

		public const string HelloEvent = "Hello";

		public const string LoadoutEvent = "Loadout";

		public const string LoadoutStateEvent = "LoadoutState";

		public const string LandingEvent = "Landing";

		public const string RejectEvent = "Reject";

		public const string AcceptedEvent = "Accepted";

		public const string ArrivedEvent = "Arrived";

		public const string SlotEvent = "Slot";

		public const string CheckpointEvent = "Checkpoint";

		public const string SentryExpiryEvent = "SentryExpiry";

		public const string DeliveryEvent = "Delivery";

		public const string PayloadDataEvent = "PayloadData";

		public const string PayloadReplayEvent = "PayloadReplay";

		public const string DeliveryReplayEvent = "DeliveryReplay";

		public const string ClaimEvent = "Claim";

		public const string ClaimStateEvent = "ClaimState";

		public const string LobbyPicks = "picks";

		public const string LobbyRules = "rules";

		public const int MaxPayloadData = 192;

		public static string Describe(Rejection reason)
		{
			return reason switch
			{
				Rejection.NotInLoadout => "not in your loadout", 
				Rejection.Cooldown => "still on cooldown", 
				Rejection.NoUses => "no uses left", 
				Rejection.TooFar => "landed too far away", 
				Rejection.InvalidLanding => "no reachable ground at the landing point", 
				Rejection.DefinitionMismatch => "your call-in files differ from the host's", 
				Rejection.NotHost => "only the host can do that", 
				Rejection.Disabled => "call-ins are disabled for you", 
				Rejection.UnknownDefinition => "unknown call-in", 
				Rejection.LoadoutInvalid => "the host refused your loadout", 
				Rejection.LandingTimeout => "the beacon never settled", 
				Rejection.CallMenuOff => "this level has no call-ins", 
				_ => "refused", 
			};
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct Vec3
	{
		public float X;

		public float Y;

		public float Z;

		public readonly bool IsFinite
		{
			get
			{
				if (float.IsFinite(X) && float.IsFinite(Y))
				{
					return float.IsFinite(Z);
				}
				return false;
			}
		}

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

		public static float Distance(Vec3 a, Vec3 b)
		{
			float num = a.X - b.X;
			float num2 = a.Y - b.Y;
			float num3 = a.Z - b.Z;
			return MathF.Sqrt(num * num + num2 * num2 + num3 * num3);
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct HelloMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Greeting;

		public uint ThrowFloor;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct LoadoutMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Player;

		public byte Count;

		public unsafe fixed uint Keys[8];

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}

		public unsafe static LoadoutMessage From(ulong player, IReadOnlyList<uint> keys)
		{
			if (keys.Count > 8)
			{
				throw new ArgumentException("Loadout is longer than " + 8 + ".", "keys");
			}
			LoadoutMessage result = new LoadoutMessage
			{
				Player = player,
				Count = (byte)keys.Count
			};
			for (int i = 0; i < keys.Count; i++)
			{
				result.Keys[i] = keys[i];
			}
			return result;
		}

		public unsafe readonly uint[]? ToKeys()
		{
			if (Count > 8)
			{
				return null;
			}
			uint[] array = new uint[Count];
			fixed (uint* keys = Keys)
			{
				for (int i = 0; i < Count; i++)
				{
					array[i] = keys[i];
				}
			}
			return array;
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct LandingMessage : INetEpoch
	{
		public ulong Epoch;

		public uint ThrowId;

		public uint Key;

		public ushort Replicator;

		public Vec3 Origin;

		public Vec3 Landing;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct RejectMessage : INetEpoch
	{
		public ulong Epoch;

		public uint ThrowId;

		public uint Key;

		public byte Reason;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct AcceptedMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Thrower;

		public uint ThrowId;

		public uint Key;

		public Vec3 Landing;

		public float Delay;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct ArrivedMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Thrower;

		public uint ThrowId;

		public uint Key;

		public Vec3 Landing;

		public float Yaw;

		public byte Arrival;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct DeliveryMessage : INetEpoch
	{
		public ulong Epoch;

		public const byte Emptied = 1;

		public const byte Failed = 2;

		public ulong Thrower;

		public uint ThrowId;

		public byte State;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct DeliveryReplayMessage : INetEpoch
	{
		public ulong Epoch;

		public const byte Pod = 1;

		public const byte Shell = 2;

		public byte Kind;

		public ulong Thrower;

		public uint ThrowId;

		public Vec3 Landing;

		public float Yaw;

		public float Elapsed;

		public float SinceEmptied;

		public uint DefinitionKey;

		public uint LookKey;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct PayloadDataMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Thrower;

		public uint ThrowId;

		public uint Type;

		public byte Tag;

		public byte Length;

		public unsafe fixed byte Data[192];

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}

		public unsafe static PayloadDataMessage From(ulong thrower, uint throwId, uint type, byte tag, ReadOnlySpan<byte> data)
		{
			if (data.Length > 192)
			{
				throw new ArgumentException("Payload data is longer than " + 192 + " bytes.", "data");
			}
			PayloadDataMessage result = new PayloadDataMessage
			{
				Thrower = thrower,
				ThrowId = throwId,
				Type = type,
				Tag = tag,
				Length = (byte)data.Length
			};
			for (int i = 0; i < data.Length; i++)
			{
				result.Data[i] = data[i];
			}
			return result;
		}

		public unsafe readonly byte[]? ToBytes()
		{
			if (Length > 192)
			{
				return null;
			}
			byte[] array = new byte[Length];
			fixed (byte* data = Data)
			{
				for (int i = 0; i < Length; i++)
				{
					array[i] = data[i];
				}
			}
			return array;
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct SlotMessage : INetEpoch
	{
		public ulong Epoch;

		public ulong Player;

		public uint Key;

		public int UsesLeft;

		public float CooldownRemaining;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct CheckpointMessage : INetEpoch
	{
		public ulong Epoch;

		public const byte Begin = 0;

		public const byte Entry = 1;

		public const byte Commit = 2;

		public byte Kind;

		public ulong Player;

		public uint Key;

		public int UsesLeft;

		public double CooldownRemaining;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct SentryExpiryMessage : INetEpoch
	{
		public ulong Epoch;

		public ushort Replicator;

		public ulong Identity;

		public float Remaining;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct ClaimMessage : INetEpoch
	{
		public ulong Epoch;

		public const byte Request = 0;

		public const byte Installed = 1;

		public const byte Failed = 2;

		public ulong Thrower;

		public uint ThrowId;

		public uint Attempt;

		public byte Action;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	internal struct ClaimStateMessage : INetEpoch
	{
		public ulong Epoch;

		public const byte Reserved = 0;

		public const byte Committed = 1;

		public const byte Cancelled = 2;

		public ulong Thrower;

		public uint ThrowId;

		public ulong Player;

		public uint Attempt;

		public byte State;

		public Guid Token;

		ulong INetEpoch.Epoch
		{
			readonly get
			{
				return Epoch;
			}
			set
			{
				Epoch = value;
			}
		}
	}
	internal static class PayloadReader
	{
		private static readonly string[] TypeField = new string[1] { "Type" };

		internal static void OnlyKnown(JsonElement payload, params string[] known)
		{
			StrictJson.OnlyKnown(payload, "", known, TypeField, (string)null);
		}

		internal static double Number(JsonElement payload, string field, double fallback, double min, double max)
		{
			return StrictJson.Number(payload, "", field, (double?)fallback, min, max);
		}

		internal static bool Bool(JsonElement payload, string field, bool fallback)
		{
			return StrictJson.Bool(payload, "", field, (bool?)fallback);
		}

		internal static (uint? Id, string? Name) Reference(JsonElement payload, string field)
		{
			return StrictJson.Reference(payload, "", field);
		}

		internal static uint ReplicatedPrefabId(uint id, string field)
		{
			if (id <= 65535)
			{
				return id;
			}
			throw new PayloadParameterException(field, "data block " + id + " cannot be spawned because the game's replicated prefab ID is limited to " + ushort.MaxValue + ".");
		}

		internal static string Look(JsonElement payload, Func<string, bool> isRegistered)
		{
			if (!payload.TryGetProperty("Look", out var value))
			{
				return "";
			}
			string text = ((value.ValueKind == JsonValueKind.String) ? value.GetString() : null);
			if (!TurretLookBook.IsLookId(text))
			{
				throw new PayloadParameterException("Look", "must be a turret look written Pack:item.");
			}
			if (!isRegistered(text))
			{
				throw new PayloadParameterException("Look", "'" + text + "' is not a registered turret look (its package is not installed, or registered it under another name).");
			}
			return text;
		}
	}
	internal static class PodTimeline
	{
		internal const float FallSeconds = 1.6f;

		internal const float SinkSeconds = 0.3f;

		internal const float PodOpenAfterImpact = 3.4f;

		internal const float StockAfterImpact = 0.4f;

		internal const float NetworkLead = 2f;

		internal const float StockDelay = 2f;

		internal const float OpenDelay = 5f;

		internal static bool MayTake(bool podFound, bool podOpen)
		{
			return !podFound || podOpen;
		}
	}
	internal sealed class RetainedData
	{
		public const int Capacity = 256;

		private readonly Dictionary<(uint Type, ulong Thrower, uint ThrowId, byte Tag), PayloadDataMessage> _latest = new Dictionary<(uint, ulong, uint, byte), PayloadDataMessage>();

		private readonly List<(uint Type, ulong Thrower, uint ThrowId, byte Tag)> _order = new List<(uint, ulong, uint, byte)>();

		public int Count => _latest.Count;

		public bool Set(PayloadDataMessage message)
		{
			(uint, ulong, uint, byte) tuple = (message.Type, message.Thrower, message.ThrowId, message.Tag);
			if (_latest.ContainsKey(tuple))
			{
				_latest[tuple] = message;
				return true;
			}
			if (_latest.Count >= 256)
			{
				return false;
			}
			_latest.Add(tuple, message);
			_order.Add(tuple);
			return true;
		}

		public int Clear(uint type, ulong thrower, uint throwId)
		{
			int num = _order.RemoveAll(((uint Type, ulong Thrower, uint ThrowId, byte Tag) k) => k.Type == type && k.Thrower == thrower && k.ThrowId == throwId);
			if (num == 0)
			{
				return 0;
			}
			foreach (var item in new List<(uint, ulong, uint, byte)>(_latest.Keys))
			{
				if (item.Item1 == type && item.Item2 == thrower && item.Item3 == throwId)
				{
					_latest.Remove(item);
				}
			}
			return num;
		}

		public void Clear()
		{
			_latest.Clear();
			_order.Clear();
		}

		public IEnumerable<PayloadDataMessage> InOrder()
		{
			foreach (var item in _order)
			{
				yield return _latest[item];
			}
		}
	}
	internal enum SentryTimerResolution
	{
		Wait,
		Bind,
		Discard
	}
	internal static class SentryIdentity
	{
		internal readonly record struct SentryMark(ulong Identity, bool NoPickup, uint LookKey);

		private const string Prefix = "FO:";

		private const int MarkerLength = 22;

		internal static string Marker(ulong identity, bool pickable, uint lookKey = 0u)
		{
			if (identity == 0L || identity > uint.MaxValue)
			{
				throw new ArgumentOutOfRangeException("identity");
			}
			return "FO:" + identity.ToString("x8", CultureInfo.InvariantCulture) + ":" + (pickable ? "P" : "U") + ":" + lookKey.ToString("x8", CultureInfo.InvariantCulture);
		}

		internal static bool TryMarker(string? marker, out ulong identity, out bool pickable)
		{
			uint lookKey;
			return TryMarker(marker, out identity, out pickable, out lookKey);
		}

		internal static bool TryMarker(string? marker, out ulong identity, out bool pickable, out uint lookKey)
		{
			identity = 0uL;
			pickable = false;
			lookKey = 0u;
			if (marker == null || marker.Length != 22 || !marker.StartsWith("FO:", StringComparison.Ordinal) || marker[11] != ':' || marker[13] != ':')
			{
				return false;
			}
			if (!ulong.TryParse(marker.Substring(3, 8), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out identity) || identity == 0L)
			{
				return false;
			}
			if (!uint.TryParse(marker.Substring(14, 8), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out lookKey))
			{
				return false;
			}
			if (marker[12] == 'P')
			{
				pickable = true;
			}
			else if (marker[12] != 'U')
			{
				return false;
			}
			return true;
		}

		internal static SentryMark Merge(SentryMark? known, SentryMark incoming)
		{
			if (known.HasValue)
			{
				SentryMark valueOrDefault = known.GetValueOrDefault();
				if (valueOrDefault.Identity == incoming.Identity)
				{
					return new SentryMark(valueOrDefault.Identity, valueOrDefault.NoPickup || incoming.NoPickup, (valueOrDefault.LookKey != 0) ? valueOrDefault.LookKey : incoming.LookKey);
				}
			}
			return incoming;
		}

		internal static SentryTimerResolution ResolveTimer(ulong expected, ulong? actual, double now, double expiresAt)
		{
			if (expected == 0L || actual != expected)
			{
				if (!(now < expiresAt))
				{
					return SentryTimerResolution.Discard;
				}
				return SentryTimerResolution.Wait;
			}
			return SentryTimerResolution.Bind;
		}
	}
	public sealed class OrdnanceLevelRules
	{
		public int LoadoutSize { get; init; } = 4;

		public IReadOnlyList<string>? Allowed { get; init; }

		public double CooldownMultiplier { get; init; } = 1.0;

		public double UsesMultiplier { get; init; } = 1.0;

		public double MaxThrowDistance { get; init; } = 80.0;

		public bool CallMenu { get; init; } = true;

		public IReadOnlyList<OrdnanceLevelDrop> Drops { get; init; } = Array.Empty<OrdnanceLevelDrop>();

		public bool IsAllowed(string id)
		{
			if (Allowed != null)
			{
				return Allowed.Contains<string>(id, StringComparer.Ordinal);
			}
			return true;
		}
	}
	internal sealed class OrdnanceSettings
	{
		public const int DefaultLoadoutSize = 4;

		public const double DefaultMaxThrowDistance = 80.0;

		private const double MaxMultiplier = 10.0;

		private const double MinThrowDistance = 5.0;

		private const double MaxThrowDistanceLimit = 500.0;

		private static readonly string[] LevelFields = new string[7] { "LoadoutSize", "Allowed", "CooldownMultiplier", "UsesMultiplier", "MaxThrowDistance", "CallMenu", "Drops" };

		private static readonly string[] RootFields = LevelFields.Concat(new string[1] { "Levels" }).ToArray();

		private readonly OrdnanceLevelRules _global;

		private readonly Dictionary<string, OrdnanceLevelRules> _levels;

		private readonly HashSet<string> _declared;

		public static OrdnanceSettings Default { get; } = new OrdnanceSettings(new OrdnanceLevelRules(), new Dictionary<string, OrdnanceLevelRules>());

		private OrdnanceSettings(OrdnanceLevelRules global, Dictionary<string, OrdnanceLevelRules> levels, HashSet<string>? declared = null)
		{
			_global = global;
			_levels = levels;
			_declared = declared ?? new HashSet<string>(StringComparer.Ordinal);
		}

		public OrdnanceLevelRules For(string? levelKey)
		{
			if (levelKey == null || !_levels.TryGetValue(levelKey, out OrdnanceLevelRules value))
			{
				return _global;
			}
			return value;
		}

		public OrdnanceSettings WithDeclared(IReadOnlyDictionary<string, OrdnanceLevelRules> declared, List<string> errors)
		{
			if (declared.Count == 0)
			{
				return this;
			}
			Dictionary<string, OrdnanceLevelRules> dictionary = new Dictionary<string, OrdnanceLevelRules>(_levels, StringComparer.Ordinal);
			HashSet<string> hashSet = new HashSet<string>(_declared, StringComparer.Ordinal);
			foreach (var (text2, value) in declared)
			{
				if (dictionary.ContainsKey(text2))
				{
					errors.Add("DeclareLevel '" + text2 + "' ignored: Settings.json Levels." + text2 + " already sets this level; the file wins.");
				}
				else
				{
					dictionary[text2] = value;
					hashSet.Add(text2);
				}
			}
			return new OrdnanceSettings(_global, dictionary, hashSet);
		}

		public string? Unmatched(string? current)
		{
			List<string> list = _levels.Keys.Where((string k) => !_declared.Contains(k)).OrderBy<string, string>((string k) => k, StringComparer.Ordinal).ToList();
			if (list.Count == 0 || (current != null && _levels.ContainsKey(current)))
			{
				return null;
			}
			return "Warning: Settings.json sets no level rules for the level being played ('" + (current ?? "unknown") + "'); it sets them only for " + string.Join(", ", list.Select((string k) => "'" + k + "'")) + ".";
		}

		public static void Check(OrdnanceLevelRules rules, string where, List<string> errors)
		{
			if (rules.LoadoutSize < 1 || rules.LoadoutSize > 8)
			{
				errors.Add(where + " LoadoutSize: must be a whole number 1.." + 8 + ".");
			}
			if (rules.Allowed != null && rules.Allowed.Any(string.IsNullOrWhiteSpace))
			{
				errors.Add(where + " Allowed: must hold definition Ids only.");
			}
			CheckRange(rules.CooldownMultiplier, 0.0, 10.0, "CooldownMultiplier", where, errors);
			CheckRange(rules.UsesMultiplier, 0.0, 10.0, "UsesMultiplier", where, errors);
			CheckRange(rules.MaxThrowDistance, 5.0, 500.0, "MaxThrowDistance", where, errors);
			DeveloperDrops.Check(rules.Drops, where, errors);
		}

		private static void CheckRange(double value, double min, double max, string field, string where, List<string> errors)
		{
			if (!double.IsFinite(value) || value < min || value > max)
			{
				errors.Add(where + " " + field + ": must be a finite number " + min.ToString(CultureInfo.InvariantCulture) + ".." + max.ToString(CultureInfo.InvariantCulture) + ".");
			}
		}

		public IEnumerable<string> UnknownAllowed(DefinitionSet set)
		{
			OrdnanceDefinition definition;
			return (from id in (from s in _levels.Values.Prepend(_global)
					where s.Allowed != null
					select s).SelectMany((OrdnanceLevelRules s) => s.Allowed)
				where !set.TryGetById(id, out definition)
				select id).Distinct<string>(StringComparer.Ordinal);
		}

		public OrdnanceSettings WithUsableDrops(DefinitionSet set, Func<string, bool> supportsDirect, List<string> errors)
		{
			Dictionary<IReadOnlyList<OrdnanceLevelDrop>, IReadOnlyList<OrdnanceLevelDrop>> checkedLists = new Dictionary<IReadOnlyList<OrdnanceLevelDrop>, IReadOnlyList<OrdnanceLevelDrop>>(ReferenceEqualityComparer.Instance);
			OrdnanceLevelRules global = Check(_global, "Settings.json");
			Dictionary<string, OrdnanceLevelRules> dictionary = new Dictionary<string, OrdnanceLevelRules>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, OrdnanceLevelRules> level in _levels)
			{
				level.Deconstruct(out var key, out var value);
				string key2 = key;
				OrdnanceLevelRules rules = value;
				dictionary[key2] = Check(rules, Where(key2));
			}
			return new OrdnanceSettings(global, dictionary, _declared);
			OrdnanceLevelRules Check(OrdnanceLevelRules ordnanceLevelRules, string where)
			{
				if (ordnanceLevelRules.Drops.Count == 0)
				{
					return ordnanceLevelRules;
				}
				if (!checkedLists.TryGetValue(ordnanceLevelRules.Drops, out IReadOnlyList<OrdnanceLevelDrop> value2))
				{
					value2 = (checkedLists[ordnanceLevelRules.Drops] = DeveloperDrops.Usable(ordnanceLevelRules.Drops, set, supportsDirect, where, errors));
				}
				return new OrdnanceLevelRules
				{
					LoadoutSize = ordnanceLevelRules.LoadoutSize,
					Allowed = ordnanceLevelRules.Allowed,
					CooldownMultiplier = ordnanceLevelRules.CooldownMultiplier,
					UsesMultiplier = ordnanceLevelRules.UsesMultiplier,
					MaxThrowDistance = ordnanceLevelRules.MaxThrowDistance,
					CallMenu = ordnanceLevelRules.CallMenu,
					Drops = value2
				};
			}
			string Where(string text)
			{
				if (!_declared.Contains(text))
				{
					return "Settings.json Levels." + text;
				}
				return "DeclareLevel " + text;
			}
		}

		public static OrdnanceSettings? Parse(string json, List<string> errors)
		{
			JsonDocument jsonDocument;
			try
			{
				jsonDocument = JsonDocument.Parse(json, new JsonDocumentOptions
				{
					CommentHandling = JsonCommentHandling.Skip,
					AllowTrailingCommas = true
				});
			}
			catch (JsonException ex)
			{
				errors.Add("Settings.json: invalid JSON: " + ex.Message);
				return null;
			}
			using (jsonDocument)
			{
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object)
				{
					errors.Add("Settings.json: must be a JSON object.");
					return null;
				}
				int count = errors.Count;
				OrdnanceLevelRules ordnanceLevelRules = Read(rootElement, new OrdnanceLevelRules(), RootFields, "Settings.json", errors);
				Dictionary<string, OrdnanceLevelRules> dictionary = new Dictionary<string, OrdnanceLevelRules>(StringComparer.Ordinal);
				if (rootElement.TryGetProperty("Levels", out var value))
				{
					if (value.ValueKind != JsonValueKind.Object)
					{
						errors.Add("Settings.json Levels: must be an object keyed by level.");
					}
					else
					{
						foreach (JsonProperty item in value.EnumerateObject())
						{
							if (item.Value.ValueKind != JsonValueKind.Object)
							{
								errors.Add("Settings.json Levels." + item.Name + ": must be an object.");
							}
							else
							{
								dictionary[item.Name] = Read(item.Value, ordnanceLevelRules, LevelFields, "Settings.json Levels." + item.Name, errors);
							}
						}
					}
				}
				return (errors.Count == count) ? new OrdnanceSettings(ordnanceLevelRules, dictionary) : null;
			}
		}

		private static OrdnanceLevelRules Read(JsonElement owner, OrdnanceLevelRules inherited, string[] known, string where, List<string> errors)
		{
			foreach (JsonProperty item in owner.EnumerateObject())
			{
				if (Array.IndexOf(known, item.Name) < 0)
				{
					errors.Add(where + " " + item.Name + ": unknown field.");
				}
			}
			int loadoutSize = inherited.LoadoutSize;
			if (owner.TryGetProperty("LoadoutSize", out var value))
			{
				if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var value2) && value2 >= 1 && value2 <= 8)
				{
					loadoutSize = value2;
				}
				else
				{
					errors.Add(where + " LoadoutSize: must be a whole number 1.." + 8 + ".");
				}
			}
			IReadOnlyList<string> allowed = inherited.Allowed;
			if (owner.TryGetProperty("Allowed", out var value3))
			{
				if (value3.ValueKind == JsonValueKind.Null)
				{
					allowed = null;
				}
				else if (value3.ValueKind != JsonValueKind.Array || value3.EnumerateArray().Any((JsonElement e) => e.ValueKind != JsonValueKind.String))
				{
					errors.Add(where + " Allowed: must be an array of definition Ids, or null for all.");
				}
				else
				{
					allowed = (from e in value3.EnumerateArray()
						select e.GetString()).ToArray();
				}
			}
			bool callMenu = inherited.CallMenu;
			if (owner.TryGetProperty("CallMenu", out var value4))
			{
				JsonValueKind valueKind = value4.ValueKind;
				if (valueKind - 5 <= JsonValueKind.Object)
				{
					callMenu = value4.GetBoolean();
				}
				else
				{
					errors.Add(where + " CallMenu: must be true or false.");
				}
			}
			JsonElement value5;
			IReadOnlyList<OrdnanceLevelDrop> drops = (owner.TryGetProperty("Drops", out value5) ? DeveloperDrops.Parse(value5, where, errors) : inherited.Drops);
			return new OrdnanceLevelRules
			{
				LoadoutSize = loadoutSize,
				Allowed = allowed,
				CallMenu = callMenu,
				Drops = drops,
				CooldownMultiplier = Finite(owner, "CooldownMultiplier", 0.0, 10.0, inherited.CooldownMultiplier, where, errors),
				UsesMultiplier = Finite(owner, "UsesMultiplier", 0.0, 10.0, inherited.UsesMultiplier, where, errors),
				MaxThrowDistance = Finite(owner, "MaxThrowDistance", 5.0, 500.0, inherited.MaxThrowDistance, where, errors)
			};
		}

		private static double Finite(JsonElement owner, string field, double min, double max, double fallback, string where, List<string> errors)
		{
			if (!owner.TryGetProperty(field, out var value))
			{
				return fallback;
			}
			if (value.ValueKind == JsonValueKind.Number && value.TryGetDouble(out var value2) && !double.IsNaN(value2) && !double.IsInfinity(value2) && value2 >= min && value2 <= max)
			{
				return value2;
			}
			errors.Add(where + " " + field + ": must be a finite number " + min.ToString(CultureInfo.InvariantCulture) + ".." + max.ToString(CultureInfo.InvariantCulture) + ".");
			return fallback;
		}
	}
	internal sealed class SlotBook
	{
		private sealed class Slot
		{
			internal double CooldownEnd;

			internal int Uses;

			internal int InitialUses;
		}

		private sealed class PlayerState
		{
			internal readonly List<uint> Loadout = new List<uint>();

			internal readonly Dictionary<uint, Slot> Slots = new Dictionary<uint, Slot>();
		}

		private readonly Dictionary<ulong, PlayerState> _players = new Dictionary<ulong, PlayerState>();

		internal ulong LoadoutVersion { get; private set; }

		public IEnumerable<ulong> Players => _players.Keys;

		public IReadOnlyList<uint> LoadoutOf(ulong player)
		{
			if (!_players.TryGetValue(player, out PlayerState value))
			{
				return Array.Empty<uint>();
			}
			return value.Loadout;
		}

		public void SetLoadout(ulong player, IReadOnlyList<OrdnanceDefinition> definitions, double usesMultiplier)
		{
			LoadoutVersion++;
			PlayerState playerState = State(player);
			HashSet<uint> hashSet = playerState.Loadout.ToHashSet();
			HashSet<uint> hashSet2 = definitions.Select((OrdnanceDefinition d) => d.Key).ToHashSet();
			foreach (var (item, slot2) in playerState.Slots)
			{
				if (hashSet.Contains(item) != hashSet2.Contains(item))
				{
					slot2.CooldownEnd = 0.0;
				}
			}
			playerState.Loadout.Clear();
			foreach (OrdnanceDefinition definition in definitions)
			{
				playerState.Loadout.Add(definition.Key);
				if (!playerState.Slots.ContainsKey(definition.Key))
				{
					int num2 = (definition.Unlimited ? (-1) : ((int)Math.Floor((double)definition.Uses * usesMultiplier + 1E-09)));
					playerState.Slots[definition.Key] = new Slot
					{
						Uses = num2,
						InitialUses = num2
					};
				}
			}
		}

		public void ApplyLoadout(ulong player, IReadOnlyList<uint> keys)
		{
			LoadoutVersion++;
			PlayerState playerState = State(player);
			playerState.Loadout.Clear();
			playerState.Loadout.AddRange(keys);
		}

		public void Apply(ulong player, uint key, int uses, double cooldownRemaining, double now)
		{
			PlayerState playerState = State(player);
			if (!playerState.Slots.TryGetValue(key, out Slot value))
			{
				Dictionary<uint, Slot> slots = playerState.Slots;
				Slot obj = new Slot
				{
					InitialUses = uses
				};
				value = obj;
				slots[key] = obj;
			}
			value.Uses = uses;
			value.CooldownEnd = now + Math.Max(0.0, cooldownRemaining);
		}

		public Rejection Check(ulong player, uint key, double now)
		{
			if (!_players.TryGetValue(player, out PlayerState value) || !value.Loadout.Contains(key) || !value.Slots.TryGetValue(key, out Slot value2))
			{
				return Rejection.NotInLoadout;
			}
			if (value2.Uses == 0)
			{
				return Rejection.NoUses;
			}
			if (now < value2.CooldownEnd)
			{
				return Rejection.Cooldown;
			}
			return Rejection.None;
		}

		public void Consume(ulong player, uint key, double now, double cooldownSeconds)
		{
			Rejection rejection = Check(player, key, now);
			if (rejection != Rejection.None)
			{
				throw new InvalidOperationException("Cannot consume: " + rejection.ToString() + ".");
			}
			Slot slot = _players[player].Slots[key];
			if (slot.Uses > 0)
			{
				slot.Uses--;
			}
			slot.CooldownEnd = now + cooldownSeconds;
		}

		public bool TryGet(ulong player, uint key, double now, out int uses, out double cooldownRemaining)
		{
			uses = 0;
			cooldownRemaining = 0.0;
			if (!_players.TryGetValue(player, out PlayerState value) || !value.Slots.TryGetValue(key, out Slot value2))
			{
				return false;
			}
			uses = value2.Uses;
			cooldownRemaining = Math.Max(0.0, value2.CooldownEnd - now);
			return true;
		}

		public void RemovePlayer(ulong player)
		{
			if (_players.Remove(player))
			{
				LoadoutVersion++;
			}
		}

		public void Clear()
		{
			_players.Clear();
			LoadoutVersion++;
		}

		public SlotSnapshot Capture(double now)
		{
			List<SlotSnapshot.Entry> list = new List<SlotSnapshot.Entry>();
			foreach (var (player, playerState2) in _players)
			{
				foreach (var (key, slot2) in playerState2.Slots)
				{
					list.Add(new SlotSnapshot.Entry(player, key, Math.Max(0.0, slot2.CooldownEnd - now), slot2.Uses));
				}
			}
			return new SlotSnapshot(list);
		}

		public SlotSnapshot RecoverySnapshot(SlotSnapshot? checkpoint)
		{
			List<SlotSnapshot.Entry> list = checkpoint?.Entries.ToList() ?? new List<SlotSnapshot.Entry>();
			HashSet<(ulong, uint)> hashSet = list.Select((SlotSnapshot.Entry e) => (Player: e.Player, Key: e.Key)).ToHashSet();
			foreach (var (num2, playerState2) in _players)
			{
				foreach (var (num4, slot2) in playerState2.Slots)
				{
					if (!hashSet.Contains((num2, num4)))
					{
						list.Add(new SlotSnapshot.Entry(num2, num4, 0.0, slot2.InitialUses));
					}
				}
			}
			return new SlotSnapshot(list);
		}

		public void Restore(SlotSnapshot snapshot, double now)
		{
			HashSet<(ulong, uint)> hashSet = new HashSet<(ulong, uint)>();
			foreach (SlotSnapshot.Entry entry in snapshot.Entries)
			{
				PlayerState playerState = State(entry.Player);
				if (!playerState.Slots.TryGetValue(entry.Key, out Slot value))
				{
					Dictionary<uint, Slot> slots = playerState.Slots;
					uint key = entry.Key;
					Slot obj = new Slot
					{
						InitialUses = entry.Uses
					};
					value = obj;
					slots[key] = obj;
				}
				value.Uses = entry.Uses;
				value.CooldownEnd = now + entry.CooldownRemaining;
				hashSet.Add((entry.Player, entry.Key));
			}
			foreach (var (item, playerState3) in _players)
			{
				foreach (var (item2, slot2) in playerState3.Slots)
				{
					if (!hashSet.Contains((item, item2)))
					{
						slot2.Uses = slot2.InitialUses;
						slot2.CooldownEnd = 0.0;
					}
				}
			}
		}

		private PlayerState State(ulong player)
		{
			if (!_players.TryGetValue(player, out PlayerState value))
			{
				value = (_players[player] = new PlayerState());
			}
			return value;
		}
	}
	internal sealed class SlotSnapshot
	{
		public readonly record struct Entry(ulong Player, uint Key, double CooldownRemaining, int Uses);

		public IReadOnlyList<Entry> Entries { get; }

		public SlotSnapshot(IReadOnlyList<Entry> entries)
		{
			Entries = entries;
		}
	}
	internal readonly record struct PendingSummon(ulong Thrower, uint ThrowId, uint Key, float X, float Y, float Z, double ArriveAt, bool Developer = false, OrdnanceArrivalMode Arrival = OrdnanceArrivalMode.Drop, float Yaw = 0f, int Dimension = -1);
	internal sealed class SummonScheduler
	{
		private readonly List<PendingSummon> _pending = new List<PendingSummon>();

		public int Count => _pending.Count;

		public PendingSummon[] Beacons()
		{
			return _pending.Where((PendingSummon p) => !p.Developer).ToArray();
		}

		public bool Has(ulong thrower, uint throwId)
		{
			return _pending.Any((PendingSummon p) => p.Thrower == thrower && p.ThrowId == throwId);
		}

		public void Add(PendingSummon summon)
		{
			if (Has(summon.Thrower, summon.ThrowId))
			{
				throw new InvalidOperationException("Throw " + summon.ThrowId + " of " + summon.Thrower + " is already scheduled.");
			}
			_pending.Add(summon);
		}

		public void TakeDue(double now, List<PendingSummon> due)
		{
			due.Clear();
			if (_pending.Count == 0)
			{
				return;
			}
			int i;
			for (i = 0; i < _pending.Count && !(_pending[i].ArriveAt <= now); i++)
			{
			}
			if (i == _pending.Count)
			{
				return;
			}
			int num = i;
			for (int j = i; j < _pending.Count; j++)
			{
				PendingSummon pendingSummon = _pending[j];
				if (pendingSummon.ArriveAt <= now)
				{
					due.Add(pendingSummon);
				}
				else
				{
					_pending[num++] = pendingSummon;
				}
			}
			_pending.RemoveRange(num, _pending.Count - num);
			for (int k = 1; k < due.Count; k++)
			{
				PendingSummon value = due[k];
				int num2 = k;
				while (num2 > 0 && due[num2 - 1].ArriveAt > value.ArriveAt)
				{
					due[num2] = due[num2 - 1];
					num2--;
				}
				due[num2] = value;
			}
		}

		public int Clear()
		{
			int count = _pending.Count;
			_pending.Clear();
			return count;
		}
	}
	internal static class ThrowValidator
	{
		internal static Rejection Evaluate(SlotBook book, bool peerCompatible, bool callMenu, ulong player, uint key, Vec3 origin, Vec3 landing, double now, double maxDistance, Func<bool> landingReachable)
		{
			if (!callMenu)
			{
				return Rejection.CallMenuOff;
			}
			if (!peerCompatible)
			{
				return Rejection.DefinitionMismatch;
			}
			Rejection rejection = book.Check(player, key, now);
			if (rejection != Rejection.None)
			{
				return rejection;
			}
			if (!origin.IsFinite || !landing.IsFinite)
			{
				return Rejection.InvalidLanding;
			}
			if ((double)Vec3.Distance(origin, landing) > maxDistance)
			{
				return Rejection.TooFar;
			}
			if (!landingReachable())
			{
				return Rejection.InvalidLanding;
			}
			return Rejection.None;
		}
	}
	internal sealed class ThrowHistory
	{
		private sealed class Peer
		{
			internal uint Floor;

			internal readonly uint[] Seen = new uint[256];
		}

		internal const int Window = 256;

		private readonly Dictionary<ulong, Peer> _peers = new Dictionary<ulong, Peer>();

		internal uint Floor(ulong player)
		{
			if (!_peers.TryGetValue(player, out Peer value))
			{
				return 0u;
			}
			return value.Floor;
		}

		internal bool Add(ulong player, uint id)
		{
			if (id == 0)
			{
				return false;
			}
			if (!_peers.TryGetValue(player, out Peer value))
			{
				_peers.Add(player, value = new Peer());
			}
			if (id <= value.Floor && value.Floor - id >= 256)
			{
				return false;
			}
			uint num = id % 256;
			if (value.Seen[num] == id)
			{
				return false;
			}
			value.Seen[num] = id;
			if (id > value.Floor)
			{
				value.Floor = id;
			}
			return true;
		}

		internal void Clear()
		{
			_peers.Clear();
		}
	}
	internal readonly record struct ToolSlotState(string Item, bool Deployed, int Status)
	{
		internal string Describe()
		{
			return Item + " (deployed " + (Deployed ? "yes" : "no") + ", status " + Status + ")";
		}
	}
	internal sealed class ToolSlotGuard
	{
		private int _depth;

		internal bool Active => _depth > 0;

		internal void Enter()
		{
			_depth++;
		}

		internal void Exit()
		{
			if (_depth > 0)
			{
				_depth--;
			}
		}

		internal bool AllowsDeployChange(bool classSlot)
		{
			return !(Active && classSlot);
		}
	}
	public sealed class TurretLookSpec
	{
		public string Base { get; set; } = "base";

		public string Yaw { get; set; } = "yaw";

		public string Head { get; set; } = "head";

		public string Muzzle { get; set; } = "muzzle";

		public float WorldScale { get; set; } = 1f;

		internal string? Problem()
		{
			string[] array = new string[4] { Base, Yaw, Head, Muzzle };
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				if (string.IsNullOrWhiteSpace(array2[i]))
				{
					return "a part name is empty";
				}
			}
			if (new HashSet<string>(array, StringComparer.Ordinal).Count != array.Length)
			{
				return "two parts have the same name";
			}
			if (!float.IsFinite(WorldScale) || WorldScale < 0.1f || WorldScale > 5f)
			{
				return "WorldScale must be between 0.1 and 5";
			}
			return null;
		}
	}
	internal sealed class TurretLookBook
	{
		private readonly Dictionary<string, TurretLookSpec> _byId = new Dictionary<string, TurretLookSpec>(StringComparer.Ordinal);

		private readonly Dictionary<uint, string> _byKey = new Dictionary<uint, string>();

		internal static bool IsLookId(string? id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return false;
			}
			int num = id.IndexOf(':');
			if (num <= 0 || num == id.Length - 1 || id.IndexOf(':', num + 1) >= 0)
			{
				return false;
			}
			foreach (char c in id)
			{
				bool flag = c != ':';
				if (flag)
				{
					bool flag2;
					switch (c)
					{
					case '-':
					case '.':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
					case 'A':
					case 'B':
					case 'C':
					case 'D':
					case 'E':
					case 'F':
					case 'G':
					case 'H':
					case 'I':
					case 'J':
					case 'K':
					case 'L':
					case 'M':
					case 'N':
					case 'O':
					case 'P':
					case 'Q':
					case 'R':
					case 'S':
					case 'T':
					case 'U':
					case 'V':
					case 'W':
					case 'X':
					case 'Y':
					case 'Z':
					case '_':
					case 'a':
					case 'b':
					case 'c':
					case 'd':
					case 'e':
					case 'f':
					case 'g':
					case 'h':
					case 'i':
					case 'j':
					case 'k':
					case 'l':
					case 'm':
					case 'n':
					case 'o':
					case 'p':
					case 'q':
					case 'r':
					case 's':
					case 't':
					case 'u':
					case 'v':
					case 'w':
					case 'x':
					case 'y':
					case 'z':
						flag2 = true;
						break;
					default:
						flag2 = false;
						break;
					}
					flag = !flag2;
				}
				if (flag)
				{
					return false;
				}
			}
			return true;
		}

		internal string? Register(string lookId, TurretLookSpec spec)
		{
			if (!IsLookId(lookId))
			{
				return "the look id must be written Pack:item (letters, digits, '_', '-', '.')";
			}
			if (spec == null)
			{
				return "no spec was given";
			}
			string text = spec.Problem();
			if (text != null)
			{
				return text;
			}
			if (_byId.ContainsKey(lookId))
			{
				return "the look is already registered";
			}
			uint num = Fnv.Hash32(lookId);
			if (num == 0 || _byKey.ContainsKey(num))
			{
				return "the look id has the same network key as another registered look; choose another name";
			}
			_byId.Add(lookId, spec);
			_byKey.Add(num, lookId);
			return null;
		}

		internal bool IsRegistered(string lookId)
		{
			return _byId.ContainsKey(lookId);
		}

		internal static uint KeyOf(string lookId)
		{
			return Fnv.Hash32(lookId);
		}

		internal bool TryGet(string lookId, out TurretLookSpec spec)
		{
			return _byId.TryGetValue(lookId, out spec);
		}

		internal bool TryGetByKey(uint key, out string lookId)
		{
			return _byKey.T