Decompiled source of ForgeOrdnance v1.0.0
ForgeOrdnance.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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